diff --git a/docs/producer-consumer.md b/docs/producer-consumer.md
index b69406edf..88e34d0d6 100644
--- a/docs/producer-consumer.md
+++ b/docs/producer-consumer.md
@@ -21,6 +21,7 @@ Enable subscribing to an instance by adding the `external_message_consumer` sect
"name": "default", // This can be any name you'd like, default is "default"
"host": "127.0.0.1", // The host from your producer's api_server config
"port": 8080, // The port from your producer's api_server config
+ "secure": false, // Use a secure websockets connection, default false
"ws_token": "sercet_Ws_t0ken" // The ws_token from your producer's api_server config
}
],
@@ -42,6 +43,7 @@ Enable subscribing to an instance by adding the `external_message_consumer` sect
| `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.
**Datatype:** string
| `producers.host` | **Required.** The hostname or IP address from your producer.
**Datatype:** string
| `producers.port` | **Required.** The port matching the above host.
**Datatype:** string
+| `producers.secure` | **Optional.** Use ssl in websockets connection. Default False.
**Datatype:** string
| `producers.ws_token` | **Required.** `ws_token` as configured on the producer.
**Datatype:** string
| | **Optional settings**
| `wait_timeout` | Timeout until we ping again if no message is received.
*Defaults to `300`.*
**Datatype:** Integer - in seconds.
diff --git a/docs/rest-api.md b/docs/rest-api.md
index c7d762648..62ad586dd 100644
--- a/docs/rest-api.md
+++ b/docs/rest-api.md
@@ -389,6 +389,44 @@ Now anytime those types of RPC messages are sent in the bot, you will receive th
}
```
+#### Reverse Proxy setup
+
+When using [Nginx](https://nginx.org/en/docs/), the following configuration is required for WebSockets to work (Note this configuration is incomplete, it's missing some information and can not be used as is):
+
+Please make sure to replace `` (and the subsequent port) with the IP and Port matching your configuration/setup.
+
+```
+http {
+ map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+ }
+
+ #...
+
+ server {
+ #...
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_pass http://:8080;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_set_header Host $host;
+ }
+ }
+}
+```
+
+To properly configure your reverse proxy (securely), please consult it's documentation for proxying websockets.
+
+- **Traefik**: Traefik supports websockets out of the box, see the [documentation](https://doc.traefik.io/traefik/)
+- **Caddy**: Caddy v2 supports websockets out of the box, see the [documentation](https://caddyserver.com/docs/v2-upgrade#proxy)
+
+!!! Tip "SSL certificates"
+ You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any fo the above reverse proxies.
+ While this will protect your data in transit, we do not recommend to run the freqtrade API outside of your private network (VPN, SSH tunnel).
+
### OpenAPI interface
To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration.
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 534d06fd4..cfac98ebd 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -512,6 +512,7 @@ CONF_SCHEMA = {
'minimum': 0,
'maximum': 65535
},
+ 'secure': {'type': 'boolean', 'default': False},
'ws_token': {'type': 'string'},
},
'required': ['name', 'host', 'ws_token']
diff --git a/freqtrade/rpc/external_message_consumer.py b/freqtrade/rpc/external_message_consumer.py
index b978407e4..6078efd07 100644
--- a/freqtrade/rpc/external_message_consumer.py
+++ b/freqtrade/rpc/external_message_consumer.py
@@ -31,6 +31,7 @@ class Producer(TypedDict):
name: str
host: str
port: int
+ secure: bool
ws_token: str
@@ -180,7 +181,8 @@ class ExternalMessageConsumer:
host, port = producer['host'], producer['port']
token = producer['ws_token']
name = producer['name']
- ws_url = f"ws://{host}:{port}/api/v1/message/ws?token={token}"
+ scheme = 'wss' if producer.get('secure', False) else 'ws'
+ ws_url = f"{scheme}://{host}:{port}/api/v1/message/ws?token={token}"
# This will raise InvalidURI if the url is bad
async with websockets.connect(
diff --git a/scripts/ws_client.py b/scripts/ws_client.py
index 090039cde..5d27f512e 100644
--- a/scripts/ws_client.py
+++ b/scripts/ws_client.py
@@ -199,6 +199,7 @@ async def create_client(
host,
port,
token,
+ scheme='ws',
name='default',
protocol=ClientProtocol(),
sleep_time=10,
@@ -211,13 +212,14 @@ async def create_client(
:param host: The host
:param port: The port
:param token: The websocket auth token
+ :param scheme: `ws` for most connections, `wss` for ssl
:param name: The name of the producer
:param **kwargs: Any extra kwargs passed to websockets.connect
"""
while 1:
try:
- websocket_url = f"ws://{host}:{port}/api/v1/message/ws?token={token}"
+ websocket_url = f"{scheme}://{host}:{port}/api/v1/message/ws?token={token}"
logger.info(f"Attempting to connect to {name} @ {host}:{port}")
async with websockets.connect(websocket_url, **kwargs) as ws:
@@ -304,6 +306,7 @@ async def _main(args):
producer['host'],
producer['port'],
producer['ws_token'],
+ 'wss' if producer.get('secure', False) else 'ws',
producer['name'],
sleep_time=sleep_time,
ping_timeout=ping_timeout,