Most home servers do not start with a reverse proxy.
They start with one service on one port. Then another one appears. Then Uptime Kuma is on :3001, a dashboard is on :8080, something else wants :9443, and before long the router has more port forwards than I am comfortable admitting.
That is the point where a reverse proxy stops being a nice extra and becomes part of the basic plumbing.
A reverse proxy gives you one clean front door for web services on a home server. It listens on normal web ports, handles HTTPS, receives requests for names like uptime.example.com or photos.example.com, and forwards each request to the right container or local service behind the scenes.
Last updated: June 2026
This is not an enterprise load-balancer guide. It is a practical homelab comparison of Caddy, Nginx Proxy Manager and Traefik for Linux home servers, Docker hosts, old workstations, mini PCs and self-hosted services that should be reachable without exposing chaos.
If you are building the whole setup from scratch, this article fits naturally after these guides:
- Linux Home Server Security Guide
- Linux Home Server Security Checklist
- UFW Firewall Rules for Home Servers
- Docker Security for Homelab Beginners
- Backing Up Docker Containers
- Uptime Kuma for Home Servers
1. The quick choice
The honest answer is that all three can work well. The better question is what kind of homelab you actually run.
| Tool | Best fit | Not ideal when |
|---|---|---|
| Caddy | You want a clean config file, automatic HTTPS, few moving parts and no web UI. | You want to click everything in a dashboard or auto-discover many Docker services from labels. |
| Nginx Proxy Manager | You want the easiest visual setup for proxy hosts, SSL certificates, redirects and basic access lists. | You dislike GUI-managed config, need everything in Git, or want advanced dynamic Docker routing. |
| Traefik | Your services already live in Docker Compose and you want routing defined beside each service. | You are new to reverse proxies and do not want to learn routers, services, entrypoints and labels yet. |
My practical recommendation:
- Use Nginx Proxy Manager for the first homelab reverse proxy when you want something working quickly.
- Use Caddy when you prefer simple text configuration and want the smallest mental overhead.
- Use Traefik when Docker Compose is already the centre of your setup and you are comfortable with labels.
For my own boring home-server brain, Caddy is the cleanest default. For helping someone else get started, Nginx Proxy Manager is easier to explain. For a larger Docker-heavy lab, Traefik starts to make sense.
2. What a reverse proxy actually does
A reverse proxy sits in front of your web applications.
Instead of exposing every service directly like this:
https://example.com:3001 -> Uptime Kuma
https://example.com:8080 -> random dashboard
https://example.com:9443 -> another admin panel
You expose only the proxy on normal web ports:
https://uptime.example.com -> reverse proxy -> uptime-kuma:3001
https://wiki.example.com -> reverse proxy -> wiki:8080
https://photos.example.com -> reverse proxy -> photos:80
The reverse proxy normally handles:
- HTTPS certificates and renewals;
- routing by domain name or subdomain;
- HTTP to HTTPS redirects;
- proxy headers such as
X-Forwarded-ForandX-Forwarded-Proto; - WebSocket forwarding for apps that need it;
- access logs;
- simple authentication or allow lists, depending on the tool.
What it does not do is magically secure bad applications. A reverse proxy does not fix default passwords, broken app authentication, missing backups, careless Docker mounts or an exposed admin dashboard with no updates.
Think of it as a front door and hallway, not the entire house.
3. The home-server layout I would use
For a simple public setup, the clean layout is this:
Internet
|
| DNS: uptime.example.com -> your public IP
|
Home router
| Forward 80/tcp and 443/tcp only
|
Linux home server
| UFW allows 80, 443 and restricted SSH
|
Reverse proxy container
|
Docker network: proxy
|
Backend containers with no public host ports
The important bit is at the bottom. Backend services should usually be reachable by the reverse proxy over a Docker network, not published to every interface on the host.
Create a shared proxy network:
docker network create proxy
Then attach the proxy and selected services to that network.
A backend service can look like this:
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
volumes:
- ./data:/app/data
expose:
- "3001"
networks:
- proxy
networks:
proxy:
external: true
The expose line documents the internal port. It does not publish the service to the host like ports does. The reverse proxy can reach uptime-kuma:3001 on the Docker network, but the router and random devices on the internet should not.
This is the habit that prevents a lot of mess.
4. Firewall notes before choosing a proxy
For a normal home server, I would start with this idea:
# SSH only from the LAN. Adjust the subnet.
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
# Public web traffic to the reverse proxy.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verbose
That belongs with the wider UFW firewall guide and SSH hardening guide.
There is one Docker-specific trap worth repeating: Docker port publishing and host firewalls can interact in surprising ways. A container published with ports: may be reachable even when you thought UFW was blocking it, depending on how Docker has inserted its firewall rules.
That is why the safest pattern is boring:
- publish only the reverse proxy on
80and443; - avoid publishing backend web apps to the host;
- put backends on a private Docker network;
- check exposed ports with
ss -tulpn,docker psand an external scan from another network.
ss -tulpn
docker ps --format "table {{.Names}} {{.Ports}}"
If the output shows ten random web ports bound to 0.0.0.0, the reverse proxy is not the problem yet. The Docker exposure model is.
5. Option one: Caddy
Caddy is the reverse proxy I would choose when I want the config to be readable six months later.
A simple Caddyfile is almost annoyingly small:
uptime.example.com {
reverse_proxy uptime-kuma:3001
}
That is the whole idea. A hostname, a block, and the backend.
Basic Caddy Docker Compose setup
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
volumes:
caddy_data:
caddy_config:
networks:
proxy:
external: true
Then create the Caddyfile:
uptime.example.com {
reverse_proxy uptime-kuma:3001
}
wiki.example.com {
reverse_proxy wiki:8080
}
Reload after changes:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
For a small homelab, this is hard to beat. No database, no admin panel, no Docker socket, no hidden UI state. Back up the compose file, Caddyfile and Caddy data volume, and you can rebuild it.
A slightly cleaner Caddyfile
I usually prefer adding comments and a small reusable header block. Not because it is perfect security, but because future me appreciates knowing why something exists.
(basic_headers) {
header {
X-Content-Type-Options nosniff
Referrer-Policy strict-origin-when-cross-origin
X-Frame-Options DENY
}
}
uptime.example.com {
import basic_headers
reverse_proxy uptime-kuma:3001
}
files.example.com {
import basic_headers
reverse_proxy filebrowser:80
}
I would not blindly add strict HSTS to every homelab domain on day one. HSTS is useful, but it can also make browser behaviour annoying while you are still moving services around. Add it later, deliberately, when the domain is stable and every subdomain is really HTTPS.
Where Caddy is strong
- Very small configuration for normal reverse proxy use.
- Excellent automatic HTTPS behaviour for public domains.
- Easy to keep in Git.
- No management UI to expose.
- No need to mount the Docker socket for basic use.
Where Caddy is weaker
- No built-in clicky dashboard like Nginx Proxy Manager.
- No native Docker-label workflow like Traefik.
- You need to edit a file and reload when services change.
Caddy is a good choice when your services are relatively stable and you value simple text config over a web interface.
6. Option two: Nginx Proxy Manager
Nginx Proxy Manager is the friendly option. It wraps Nginx in a web interface and gives you proxy hosts, SSL certificates, redirects, streams, access lists and users without making you write Nginx config for every service.
That is exactly why it is popular in homelabs. It removes the first wall people usually hit: certificate and Nginx syntax confusion.
Basic Nginx Proxy Manager Docker Compose setup
services:
npm:
image: jc21/nginx-proxy-manager:2.15.1
restart: unless-stopped
ports:
- "80:80"
- "443:443"
# Use 127.0.0.1 if you administer through an SSH tunnel.
# Use your LAN IP instead if you need access from another trusted LAN device.
- "127.0.0.1:81:81"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
Bring it up:
docker compose up -d
Then log into the admin interface, complete the initial setup, and do the boring things immediately:
- use a strong unique admin password;
- do not forward port
81on your router; - keep the admin UI LAN-only, VPN-only or behind an SSH tunnel;
- back up
./dataand./letsencrypt; - do not proxy sensitive admin panels until their own authentication is fixed.
To add Uptime Kuma, create a proxy host in the UI:
- Domain Names:
uptime.example.com - Scheme:
http - Forward Hostname/IP:
uptime-kuma - Forward Port:
3001 - Websockets Support: enable when the application needs it
- SSL: request a certificate and force SSL
The backend container must be on the same Docker network as Nginx Proxy Manager, otherwise the hostname uptime-kuma will not resolve.
Where Nginx Proxy Manager is strong
- Fastest path for beginners.
- Good UI for adding and editing proxy hosts.
- Easy SSL certificate management.
- Access lists and basic HTTP authentication are available from the UI.
- Useful when more than one person may need to understand the setup.
Where Nginx Proxy Manager is weaker
- The UI is another admin surface to protect.
- Configuration lives partly in application state, not just tidy files.
- Advanced Nginx changes can become less obvious than a hand-written config.
- Backups matter because certificates and proxy host state live under its data folders.
Nginx Proxy Manager is the one I would recommend to someone who wants to stop exposing random ports today and does not care about winning a configuration-purity contest.
7. Option three: Traefik
Traefik is different. It is built around dynamic routing. In a Docker homelab, that usually means the reverse proxy reads labels from containers and builds routes from those labels.
That can be elegant:
labels:
- traefik.enable=true
- traefik.http.routers.uptime.rule=Host(`uptime.example.com`)
- traefik.http.routers.uptime.entrypoints=websecure
- traefik.http.routers.uptime.tls.certresolver=le
- traefik.http.services.uptime.loadbalancer.server.port=3001
The route lives next to the service. When you move the stack, the routing information moves with it.
The price is vocabulary. Traefik has entrypoints, routers, services, middlewares, providers, certificate resolvers and labels. None of that is impossible, but it is a lot for a first reverse proxy.
Basic Traefik Docker Compose setup
Create storage for certificates:
mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
Then create the Traefik stack:
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.le.acme.email=you@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
networks:
- proxy
networks:
proxy:
external: true
Now add labels to a service:
services:
uptime-kuma:
image: louislam/uptime-kuma:1
restart: unless-stopped
volumes:
- ./data:/app/data
networks:
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.uptime.rule=Host(`uptime.example.com`)
- traefik.http.routers.uptime.entrypoints=websecure
- traefik.http.routers.uptime.tls.certresolver=le
- traefik.http.services.uptime.loadbalancer.server.port=3001
networks:
proxy:
external: true
The important safety setting is this one:
--providers.docker.exposedbydefault=false
Without that style of opt-in thinking, dynamic routing can become a fancy way to expose things you forgot existed.
Traefik dashboard warning
Traefik has a dashboard, but do not expose it casually. For a home server, it is fine to leave it disabled until you actually need it. When enabled, protect it with authentication and keep it off the public internet unless you fully understand the route and middleware protecting it.
The Docker socket mount also deserves respect. Even read-only access exposes a lot of information about your containers. For a stricter setup, look into a Docker socket proxy and give Traefik only the API access it needs.
Where Traefik is strong
- Excellent fit for Docker Compose-heavy setups.
- Routing can live next to the service definition.
- Good middleware model for redirects, headers and authentication.
- Works well when services appear, disappear or move often.
- Useful observability options when the homelab grows up a bit.
Where Traefik is weaker
- More concepts than Caddy or Nginx Proxy Manager.
- Labels can become noisy in Compose files.
- Bad labels can make routing failures harder to read at first.
- Docker socket access needs careful thought.
Traefik is powerful, but I would not make it the first reverse proxy lesson unless the person already likes Docker Compose and wants to learn the model properly.
8. Caddy vs Nginx Proxy Manager vs Traefik: practical comparison
| Question | Caddy | Nginx Proxy Manager | Traefik |
|---|---|---|---|
| Easiest first setup? | Easy if you like files | Usually easiest | Harder |
| Best GUI? | No built-in GUI | Yes | Dashboard, but not for casual config |
| Best config-as-code? | Very good | Mixed | Very good |
| Best Docker integration? | Manual unless extended | Works well with shared networks | Best native label workflow |
| Certificate handling? | Excellent | Easy through UI | Powerful but more config |
| Smallest mental load? | Usually Caddy | Usually NPM for visual users | Not Traefik |
| Best for many changing services? | Good but manual | Good but click-heavy | Strong |
| Biggest security mistake? | Over-trusting headers or exposing backends anyway | Exposing the admin UI | Exposing too much through labels or dashboard |
9. Public access, private access and split DNS
Not every homelab service needs to be public.
I would divide services into three groups:
- Public: things intentionally reachable from the internet, such as a public status page or a small website.
- Private remote: things reachable through VPN, Tailscale, WireGuard or another trusted tunnel.
- LAN only: things that should work only at home.
A reverse proxy can help all three, but the DNS and firewall design changes.
For public services, a normal flow is:
public DNS -> router forwards 80/443 -> reverse proxy -> backend
For private services, I prefer:
VPN/Tailscale/WireGuard -> local DNS -> reverse proxy -> backend
That second pattern gives you clean names and HTTPS-like structure without putting everything on the public internet.
For sensitive dashboards, private access is usually the better default. A reverse proxy is convenient, but convenience should not turn every admin panel into a public target.
10. The Docker port rule that saves headaches
For backend services, avoid this unless you really need host access:
ports:
- "8080:80"
Prefer this pattern for services that only the reverse proxy should reach:
expose:
- "80"
networks:
- proxy
Again, expose is not a magic security control. The actual protection comes from not publishing the port to the host and from keeping the service on the right Docker network.
After starting a stack, check what Docker published:
docker ps --format "table {{.Names}} {{.Ports}}"
Good:
NAMES PORTS
caddy 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
uptime-kuma 3001/tcp
Messy:
NAMES PORTS
caddy 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp
uptime-kuma 0.0.0.0:3001->3001/tcp
admin-panel 0.0.0.0:9443->9443/tcp
random-app 0.0.0.0:8080->80/tcp
The messy version defeats half the point of the reverse proxy.
11. Backups for a reverse proxy
A reverse proxy feels disposable until the certificates, routes and weird application headers disappear.
Back up at least this:
- Docker Compose files;
.envfiles, stored safely;- Caddyfile or Traefik static/dynamic config;
- Nginx Proxy Manager
datafolder; - certificate storage, such as Caddy data, NPM
letsencrypt, or Traefikacme.json; - a short note explaining which domains point where.
This belongs in the same backup routine as the rest of your Docker services. The Docker backup guide covers the bigger approach: back up the data needed to recreate the service, not just the container that happens to be running today.
12. Monitoring the proxy
Once the reverse proxy becomes the front door, monitor it.
At minimum, add Uptime Kuma checks for:
- the public homepage of each important service;
- the reverse proxy host itself;
- certificate expiry where supported;
- the backend service directly from the LAN, when useful;
- disk space on the Docker host.
The reason is simple: users do not care whether the app, proxy, DNS, router or certificate failed. They care that the page is dead.
The Uptime Kuma guide is the natural next step after setting this up.
13. Common reverse proxy problems
502 Bad Gateway
This usually means the proxy cannot reach the backend.
Check:
- wrong container name;
- wrong backend port;
- proxy and backend not on the same Docker network;
- backend app is not actually running;
- app listens only on
127.0.0.1inside its own container or VM.
Certificate fails
Check:
- DNS points to the right public IP;
- ports
80and443reach the proxy; - your ISP is not blocking inbound ports;
- you are not behind CGNAT without a workaround;
- IPv6 records are not pointing somewhere broken;
- you have not hit certificate authority rate limits while testing.
The app logs everyone as the proxy IP
The application may need to trust proxy headers. Look for settings related to trusted proxies, real IP headers, forwarded headers or external URL. Do not blindly trust forwarded headers from the whole internet; trust only the reverse proxy.
Login or redirects break
The app may not know its public URL. Set the application base URL, external URL or trusted domain setting. This is common with self-hosted apps that generate absolute redirects.
WebSockets do not work
Some dashboards, terminals and live apps need WebSocket support. Caddy and Traefik usually handle this cleanly in normal setups. In Nginx Proxy Manager, enable WebSocket support for that proxy host when required.
UFW says denied, but the container is still reachable
Assume Docker firewall behaviour is involved. Review the Docker security guide and check whether you published the backend port to the host. Start by removing unnecessary ports: mappings.
14. My final recommendation
For a Linux home server, the best reverse proxy is the one you can understand during a boring Sunday maintenance session.
Use Caddy when you want clean files, easy HTTPS and minimal moving parts.
Use Nginx Proxy Manager when you want a friendly web UI and the fastest path to sensible proxy hosts.
Use Traefik when your homelab is already Docker Compose-heavy and you want each service to carry its own routing labels.
Whatever you choose, the security model should stay the same:
- only expose what needs to be exposed;
- publish the proxy, not every backend container;
- keep admin dashboards private where possible;
- restrict SSH;
- back up the proxy config and certificates;
- monitor the services users actually open.
A reverse proxy should make your homelab boring in a good way. One entry point, clear names, HTTPS, fewer random ports, and less archaeology when something breaks.
Frequently asked questions
Do I need a reverse proxy for a home server?
You do not need one for a single local-only service. You probably want one when you run several web services, want clean subdomains, need HTTPS, or want to stop exposing random ports for every container.
Which reverse proxy is best for beginners?
Nginx Proxy Manager is usually the easiest beginner option because the UI makes proxy hosts and certificates understandable. Caddy is also beginner-friendly for people who are comfortable editing small config files.
Which reverse proxy is best for Docker?
Traefik has the strongest Docker-label workflow. Caddy and Nginx Proxy Manager still work very well with Docker when you use a shared Docker network and avoid publishing backend ports.
Is Caddy better than Nginx Proxy Manager?
Caddy is better when you want simple, readable configuration and fewer moving parts. Nginx Proxy Manager is better when a web UI helps you manage proxy hosts, certificates and access lists.
Is Traefik overkill for a homelab?
Sometimes. Traefik is excellent in a Docker-heavy setup with many services, but it can be unnecessary complexity for three simple web apps. Start with the simplest tool you will actually maintain.
Should I expose Portainer, Proxmox or router dashboards through a reverse proxy?
Not by default. Sensitive admin panels are better kept behind a VPN, Tailscale, WireGuard or LAN-only access. A reverse proxy can add convenience, but it should not turn every management interface into a public service.
Does UFW protect Docker containers behind a reverse proxy?
UFW helps, but Docker has its own firewall behaviour when ports are published. The safer habit is to avoid publishing backend container ports to the host and let only the reverse proxy expose 80 and 443.
Further reading
- Start Here: Linux Homelab and Security
- Linux Home Server Security Guide
- UFW Firewall Rules for Home Servers
- Docker Security for Homelab Beginners
- Backing Up Docker Containers
- Uptime Kuma for Home Servers
- Caddy reverse proxy documentation
- Nginx Proxy Manager guide
- Traefik Docker provider documentation
- Docker firewall and iptables documentation
Written by MS
MS is a Linux homelab and cybersecurity enthusiast who documents practical experiments with home servers, Docker, firewalls, backups, Lynis, Fail2ban, honeypots and old hardware. The guides on IT Random Stuff are based on hands-on testing, real configurations and lessons learned from running Linux systems at home.
Comments
Post a Comment