Skip to main content

Reverse Proxy for Home Servers: Caddy vs Nginx vs Traefik

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:

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-For and X-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 80 and 443;
  • avoid publishing backend web apps to the host;
  • put backends on a private Docker network;
  • check exposed ports with ss -tulpn, docker ps and 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 81 on your router;
  • keep the admin UI LAN-only, VPN-only or behind an SSH tunnel;
  • back up ./data and ./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;
  • .env files, stored safely;
  • Caddyfile or Traefik static/dynamic config;
  • Nginx Proxy Manager data folder;
  • certificate storage, such as Caddy data, NPM letsencrypt, or Traefik acme.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.1 inside its own container or VM.

Certificate fails

Check:

  • DNS points to the right public IP;
  • ports 80 and 443 reach 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

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

Popular posts from this blog

OpenCanary Honeypot on Ubuntu: 2026 Home Lab Setup Guide

I first wrote about OpenCanary years ago, and that old version badly needed an update. The tool is still useful, but the way I would deploy it in a home lab today is different: newer Ubuntu versions, Python 3, Docker as an option, cleaner logging, and no copy-pasted example credentials that look like something someone might actually reuse. This guide is the updated version: how I would set up an OpenCanary honeypot on Ubuntu in 2026 for a small home network or homelab. The goal is not to trap random people on the internet. The goal is to create a quiet internal warning system: a fake service that should never be touched, so any connection to it deserves attention. Important: this is a defensive security guide. Run OpenCanary only on systems and networks you own or have permission to monitor. Do not expose a honeypot to the public internet unless you understand the logging, legal, abuse and maintenance implications. If you are building a Linux home server security setup fro...

Lenovo ThinkPad X250 on Linux: Tweaks, Undervolting, Battery Life and 2026 Update

I wanted a cheap, small, serviceable Linux laptop. Something light enough to carry, easy enough to repair, and inexpensive enough that upgrades would still make sense. The Lenovo ThinkPad X250 was a good candidate because it has a 12.5-inch form factor, a proper ThinkPad keyboard, SSD upgrade options, replaceable parts, Ethernet, docking support and generally good Linux compatibility. I found one on eBay for around 130€ : an Intel Core i5-5300U model with 8GB RAM , a 128GB SSD , two batteries and an HD screen with a small bruise. The plan was simple: clean it, repaste it, upgrade the SSD, install Linux Mint, undervolt it and see how useful it could still be. This post started as my original 2019 notes about tweaking the Lenovo X250 in Linux. I have now updated it with a 2026 perspective, cleaner instructions, better internal links and a more realistic look at whether this old ThinkPad is still worth using. Related posts: Linux Home Server Security Checklist Docker Secu...

Strong, Unique Passwords Without Losing Your Mind

Last updated: May 27, 2026 Password Security in 2026: Password Managers, Passkeys & 2FA for Real People Password Security in 2026: Password Managers, Passkeys & 2FA That Actually Work Most people do not have a weak-password problem. They have a reused-password problem. You can invent the cleverest password in the world, but if you use it on twenty websites and one of them gets breached, you suddenly have twenty compromised accounts. That is how most real-world account takeovers happen in 2026. Not elite hackers brute-forcing your login from a dark room somewhere. Just automated credential stuffing using databases leaked years ago from services you forgot existed. One old forum breach becomes access to your email, cloud storage, streaming services, VPN account, and eventually your homelab dashboard because the same password got reused everywhere. This guide explains how to handle passwords properly today: without paranoia, without enterprise co...