Reverse Proxy
FediSuite listens on port 3000 and without a reverse proxy is only reachable directly through this port, without HTTPS and without a domain. A reverse proxy accepts requests on ports 80 and 443, handles TLS and forwards traffic to FediSuite. This page shows Traefik with Let's Encrypt and also Caddy and Nginx.
On this page
What is a reverse proxy?
A reverse proxy sits between the internet and your application. When someone opens https://your-domain.com, it accepts the request, handles the HTTPS encryption and forwards it unencrypted to FediSuite on port 3000, inside the Docker network or the host.
Traefik is a reverse proxy built for Docker environments. It detects running containers, reads their configuration from labels and sets up routing and TLS certificates by itself.
trust proxy = 1) and reads the client IP from its X-Forwarded-For header. The IP-based limiting of login attempts depends on that. If a second proxy or a CDN sits in front, FediSuite sees the address of the first proxy instead of the visitors'.
What the data flow looks like
Browser
https://your-domain.com
Port 443 (HTTPS)
Traefik
TLS termination
Port 3000 (internal)
FediSuite app
Docker network
Important: remove ports: 3000
With a reverse proxy, port 3000 should no longer be published on all interfaces of the host. The mapping 3000:3000 in the app service makes the app reachable directly on the server, without HTTPS and bypassing the proxy. Docker also bypasses firewall rules such as ufw when doing so. Traefik reaches the app over the Docker network and does not need the mapping.
Without a reverse proxy (with port mapping)
app:
...
ports:
- "3000:3000" # remove with a proxy
...
With Traefik (no port mapping)
app:
...
# ports: removed completely
labels:
- "traefik.enable=true"
...
loadbalancer.server.port=3000. If your reverse proxy runs directly on the host (Caddy, Nginx), use "127.0.0.1:3000:3000" instead of removing the mapping. Then only the proxy can reach the port.
Scenario 1: External Traefik
This is the typical case when other services already run behind Traefik on the server. Traefik has its own stack, and FediSuite joins the existing external Docker network, usually proxy.
Check the prerequisites
Your external Traefik has to meet the following:
--providers.docker=true).
tls.certresolver label. In the example it is called letsEncrypt.
proxy.
Create the external network (if it does not exist yet)
The network proxy has to be created once if it does not exist yet:
docker network create proxy
Adapt the docker-compose.yml
The docker-compose.yml already contains the labels and the network entries as comments. Make these changes in the app service (the rest of the service stays unchanged):
① Remove ports: in the app service, uncomment labels and networks and adjust the domain and resolver:
app:
...
# ports: removed, Traefik takes over
labels:
- "traefik.enable=true"
- "traefik.http.routers.fedisuite.rule=Host(`your-domain.com`)"
- "traefik.http.services.fedisuite.loadbalancer.server.port=3000"
- "traefik.http.routers.fedisuite.service=fedisuite"
- "traefik.http.routers.fedisuite.tls.certresolver=letsEncrypt"
- "traefik.docker.network=proxy"
networks:
- default
- proxy
② At the end of the file, uncomment the networks: block:
networks:
proxy:
external: true
external: true tells Docker Compose that the network already exists and must not be created.
docker-compose.override.yml (it is listed in the .gitignore). Compose appends lists such as labels and networks; from Docker Compose 2.24 you remove the ports mapping there with ports: !reset []. The file docker-compose.traefik.example.yml in the repository shows the labels and networks as a template.
Restart the stack
docker compose up -d
Traefik detects the new container, reads the labels and issues a Let's Encrypt certificate for your domain. Traefik only forwards to containers whose health check passes. Until the app is ready (up to ten minutes for long database migrations) a 404 can therefore appear.
Scenario 2: Traefik in the docker-compose.yml
If FediSuite is the only service on the server, Traefik can be added straight to the docker-compose.yml. Traefik then runs as a fifth container in the same stack, without an external network.
Add the Traefik service
Add the traefik service at the top of the docker-compose.yml under services:. Replace the email address with your real one, Let's Encrypt uses it for certificate notices.
services:
traefik:
image: traefik:v3
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsEncrypt.acme.tlschallenge=true"
- "--certificatesresolvers.letsEncrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsEncrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./letsencrypt:/letsencrypt"
networks:
- proxy
db:
# ... unchanged ...
app:
# ... see step 2 ...
:ro. Anyone who wants to avoid that puts a socket proxy in front. The Let's Encrypt TLS challenge runs over port 443, port 80 is used for the redirect to HTTPS.
Adapt the app service
Remove ports:, add the labels and the proxy network. The rest of the app service stays as in the bundled file:
app:
...
# ports: removed, Traefik takes over
labels:
- "traefik.enable=true"
- "traefik.http.routers.fedisuite.rule=Host(`your-domain.com`)"
- "traefik.http.services.fedisuite.loadbalancer.server.port=3000"
- "traefik.http.routers.fedisuite.service=fedisuite"
- "traefik.http.routers.fedisuite.tls.certresolver=letsEncrypt"
- "traefik.docker.network=proxy"
networks:
- default
- proxy
Define the network at the end of the file
Because Traefik and FediSuite run in the same stack, the network is not external. Docker Compose creates it itself:
networks:
proxy:
No external: true, the network belongs to this stack.
Start the stack
docker compose up -d
Traefik starts, detects the app container through the Docker provider and issues a TLS certificate for your domain. The certificate is stored persistently in ./letsencrypt/acme.json and renewed before it expires. Back this folder up too if you want to be able to restore the stack.
Scenario 3: Caddy or Nginx on the host
Any reverse proxy that can forward HTTP to port 3000 works. Restrict the port mapping to the local interface (127.0.0.1:3000:3000) and make sure the proxy terminates HTTPS. Note that FediSuite accepts uploads of up to 50 MB per file; a proxy with a small body limit (1 MB by default in Nginx) rejects larger attachments.
your-domain.com {
reverse_proxy 127.0.0.1:3000
}
server {
listen 443 ssl;
server_name your-domain.com;
# ssl_certificate ... (certificate as set up by you)
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Enter the public HTTPS address in the .env as APP_URL, not the local one.
Traefik labels explained
Through labels in the docker-compose.yml FediSuite tells Traefik how to route. Each label has a clear meaning:
traefik.enable=true
Enables Traefik for this container. With exposedbydefault=false (scenario 2, common in external setups) every container Traefik is meant to manage has to carry this label.
traefik.http.routers.fedisuite.rule=Host(`your-domain.com`)
Defines for which domain Traefik forwards requests to this container. Replace your-domain.com with your domain. The name fedisuite in the label is a freely chosen internal identifier of the router and only has to be unique.
traefik.http.services.fedisuite.loadbalancer.server.port=3000
Port the container listens on internally. FediSuite uses 3000. Traefik forwards to this port internally, it does not need to be opened to the outside.
traefik.http.routers.fedisuite.service=fedisuite
Links the router to the service above. With a single container this is usually implicit, but stating it explicitly avoids ambiguity.
traefik.http.routers.fedisuite.tls.certresolver=letsEncrypt
Enables TLS for this router and names the cert resolver that issues the certificate. The name letsEncrypt has to match the name of the ACME resolver in the Traefik configuration exactly.
traefik.docker.network=proxy
Tells Traefik which Docker network to use to reach the container. Because app is in two networks (default and proxy), Traefik has to know which one to use.
Troubleshooting
No certificate / browser shows a certificate error
- The DNS record of the domain does not point to the server IP yet. Wait until it takes effect.
-
Port 443 is closed in the firewall. The Let's Encrypt TLS challenge (
tlschallenge) runs over port 443. With an HTTP challenge it would be port 80. - The name of the cert resolver in the label does not match the one configured in Traefik.
- The Let's Encrypt rate limit was reached. Wait a few hours and try again.
Traefik does not find the container / 404
-
traefik.enable=trueis missing in theappservice. -
The label
traefik.docker.network=proxyis missing or names the wrong network. -
The
appcontainer is not in theproxynetwork (networks:is missing in the service). -
The health check of the
apphas not passed yet. Checkdocker compose psand wait until it ishealthy. -
docker compose up -dwas not run after the change.
FediSuite answers, but over HTTP instead of HTTPS
-
The redirect from HTTP to HTTPS is not configured in Traefik. In scenario 2 the
redirectionslines in the Traefik command are needed. -
The
tls.certresolverlabel is missing, without it Traefik sets up no TLS.
Login or account connection fails
-
APP_URLin the.envdoes not match the public HTTPS address. The OAuth return address is built from it. After a change rundocker compose up -d. - More than one proxy sits in front of the app, so the client IPs used for limiting login attempts are wrong.
Port 3000 is reachable from outside although Traefik is running
-
The line
ports: - "3000:3000"was not removed from thedocker-compose.yml. Remove it and rundocker compose up -d.
# Scenario 1: Traefik in its own stack
docker logs traefik -f
# Scenario 2: Traefik in the FediSuite stack
docker compose logs traefik -f