Traefik 2 and Gotify's Websockets

Getting Gotify's Websockets to work through Traefik v2, fixing the 502 Bad Gateway error that the Gotify Android app was throwing.

Traefik 2 and Gotify's Websockets

So, I've been spending the last few days trying to move Gotify from my nginx configuration to Traefik 2.

At first glance, this seemed easy, but I noticed that the Gotify Android app would continuously loose connection after a few minutes. This didn't occur on nginx, which I found weird. What made it even harder, it only seemed to occur with the Android client and was clearly only related to Websockets.

The log of the Android app just showed a 502 Bad Gateway error.

2020-10-05T12:36:29.242Z INFO: WebSocket: scheduling a restart in 1200 second(s) (via alarm manager)
2020-10-05T12:36:29.217Z ERROR: WebSocket(86): failure StatusCode: 502 Message: Bad Gateway
java.net.ProtocolException: Expected HTTP 101 response but was '502 Bad Gateway'
	at okhttp3.internal.ws.RealWebSocket.checkResponse(RealWebSocket.java:229)
	at okhttp3.internal.ws.RealWebSocket$2.onResponse(RealWebSocket.java:196)
	at okhttp3.RealCall$AsyncCall.execute(RealCall.java:206)
	at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:919)

2020-10-05T12:36:29.066Z INFO: WebSocket(86): starting...
2020-10-05T12:36:29.064Z INFO: WebSocket(85): closing existing connection.
2020-10-05T12:25:39.588Z INFO: Entering LogsActivity

After many days of trying different configurations, I found one that solves this issue and makes Traefik 2 play nice with Gotify.

Gotify's docker-compose.yml

The Gotify docker-compose.yml requires a bunch of labels, except for the SSL config, which in my case made more sense to have in the core traefik configuration.

version: '3.3'
networks:
  traefik_default:
    external: true
services:
    server:
        networks:
            - 'traefik_default'
        container_name: gotify
        volumes:
            - '/foo/bar/gotify:/app/data'
        image: gotify/server
        labels:
            - "traefik.enable=true"

            # Traefik Redirect to HTTPS
            - "traefik.http.middlewares.redirect-https.redirectScheme.scheme=https"
            - "traefik.http.middlewares.redirect-https.redirectScheme.permanent=true"

            # Traefik Router + Service Configuration
            - "traefik.http.routers.gotify-server.rule=Host(`gotify.domain.tld`)"
            - "traefik.http.routers.gotify-server.entrypoints=websecure"
            - "traefik.http.routers.gotify-server.tls=true"
            - "traefik.http.routers.gotify-server.tls.certresolver=lewildcardresolver"
            - "traefik.http.routers.gotify-server.middlewares=redirect-https"
            - "traefik.http.routers.gotify-server.service=gotify-server"
            - "traefik.http.services.gotify-server.loadbalancer.passhostheader=true"
            - "traefik.http.services.gotify-server.loadbalancer.server.port=80"
            - "traefik.http.services.gotify-server.loadbalancer.sticky=true"
            - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto = http"

Keep in mind the configuration above refers to lewildcardresolver which is not part of this configuration file. You should refer here to your own SSL resolver configuration.

Push to your hearts content

That's it. This should resolve any timeout issues and 502 errors you may be seeing when using Gotify behind Traefik 2.

The only suboptimal thing here is that all of your services behind the websecure entrypoint are sharing this configuration. I haven't found a way to apply this configuration to only the Gotify service. If anyone knows a trick to do that, please let me know in a comment below.