Caddy vs Nginx Proxy Manager: Reverse Proxy Setup for Self-Hosted Apps

Published

Once you’re running more than one or two self-hosted apps (Nextcloud, Immich, Uptime Kuma, a Home Assistant dashboard), you need something in front of them that routes a domain/subdomain to the right container and handles HTTPS — that’s a reverse proxy’s whole job. Caddy and Nginx Proxy Manager are the two most common picks for a homelab or small-office self-hosting setup, and they solve the same problem in genuinely different ways.

Both need a domain name pointed at your IP to issue a certificate against — if that IP isn’t static, see DuckDNS vs No-IP vs Cloudflare for keeping it resolvable as your ISP-assigned address changes.

Search intent

Someone searching this is already running (or about to run) multiple self-hosted apps behind Docker and needs to pick a reverse-proxy tool to route domains/subdomains to them with automatic HTTPS, before wiring up their next service.

The core difference: config-as-code vs GUI

Caddy is a single Go binary that acts as a web server and reverse proxy, configured through a plain-text Caddyfile (or a JSON API, if you want programmatic config). Its headline feature is fully automatic HTTPS — point a domain at it and it obtains and renews Let’s Encrypt (or ZeroSSL) certificates with no extra steps. There’s no built-in web GUI; you edit the Caddyfile as text.

Nginx Proxy Manager (NPM) is a Docker-packaged application that puts a web-based admin UI on top of nginx plus Let’s Encrypt integration. You add a “proxy host” through the browser (domain, target container/port, request a cert with one click) instead of hand-writing nginx config blocks. It’s specifically built to make nginx reverse-proxying approachable without editing config files.

That one difference — text config vs. browser GUI — drives almost every other tradeoff below.

Setup experience

NPM is the easier on-ramp if you’ve never configured a reverse proxy before: install the Docker container, log into the web UI (default port 81), click “Add Proxy Host,” fill in the domain and target, click “Request SSL Certificate.” No syntax to learn.

Caddy has a steeper first five minutes (you need to write a Caddyfile), but the syntax is deliberately terse — a working reverse-proxy block is typically 3-4 lines:

app.example.com {
    reverse_proxy 192.168.1.50:8080
}

That’s a complete, working HTTPS reverse proxy entry — no separate cert request step, because Caddy handles it automatically the moment it sees a domain name in the config.

Automatic HTTPS

This is Caddy’s signature feature: certificate acquisition and renewal are fully automatic and require no per-site action — add a domain to the Caddyfile, reload, done. NPM also automates renewal once a certificate is issued, but issuing one is a manual click per proxy host through the UI, and misconfigured DNS/port-forwarding at that step is a common first-time snag for NPM users (Caddy hits the same DNS/port-forwarding prerequisites, but there’s one less UI step to get wrong).

Config as code vs. config in a database

Caddy’s Caddyfile is plain text — trivial to put in git alongside a docker-compose.yml, diff, and roll back. This matters if you manage your homelab/office infra with any kind of version control discipline, or want to reproduce your setup on new hardware quickly.

NPM stores its configuration in a SQLite database behind the GUI. It has since added a REST API you can script against, but the primary workflow is still point-and-click through the browser, which is harder to version-control or reproduce from scratch without manually re-clicking through the UI (or scripting the API yourself).

Extensibility and protocol support

Caddy supports HTTP/3 out of the box and has a plugin ecosystem (via xcaddy, which compiles custom modules into the binary) for things like DNS-01 challenge providers for wildcard certs behind CGNAT, or Cloudflare-specific integrations. Extending it requires rebuilding the binary with the plugins you want, which is a bit more friction than a pure config change.

NPM is nginx under the hood, so it inherits nginx’s maturity and raw performance, but the GUI itself doesn’t expose a plugin system — you get what the NPM project has built into the interface. If you need something nginx can do that NPM’s UI doesn’t expose, you’re editing nginx config directly, which defeats the point of using NPM.

Resource footprint

Both are lightweight enough that this rarely decides it for a homelab or small office: Caddy ships as one static binary with a small memory footprint; NPM bundles nginx, a Node.js backend for the GUI/API, and Certbot-equivalent cert handling into one container, which is slightly heavier but still trivial on any hardware that’s already running Docker for other self-hosted apps.

Head-to-head at a glance

CaddyNginx Proxy Manager
Config styleText (Caddyfile) or JSON APIWeb GUI (SQLite-backed, has a REST API)
Automatic HTTPSFully automatic, zero extra stepsAutomatic renewal; manual cert request per host
Best for beginnersModerate learning curve (Caddyfile syntax)Easiest — no config file editing required
Version-control friendlyYes — plain textHarder — GUI-driven config
HTTP/3 supportYes, built inDepends on bundled nginx version
ExtensibilityPlugins via xcaddy rebuildLimited to what the GUI exposes
Underlying engineNative Go HTTP servernginx

Which one should you actually pick?

  • You’re comfortable with a text config file and want something reproducible/version-controllable: Caddy — the Caddyfile is genuinely simple, and automatic HTTPS with zero extra clicks is hard to beat once you’re managing more than a couple of domains.
  • You want to add proxy hosts through a browser and never touch a config file: Nginx Proxy Manager — the GUI removes the learning curve entirely, at the cost of a setup that’s harder to reproduce or track in git.
  • You’re already comfortable with raw nginx and just want a GUI for convenience: NPM keeps you on familiar ground (it’s nginx) while adding the point-and-click layer.
  • You’re putting a self-hosted Git forge behind HTTPS on your own domain: either proxy works fine — see Gitea vs GitLab CE vs Forgejo for picking the forge itself first.
  • You’re putting self-hosted team chat, video calling, or a helpdesk behind HTTPS: same setup applies — see Rocket.Chat vs Mattermost vs Zulip, Jitsi Meet vs Zoom vs Google Meet, and Zammad vs FreeScout vs Zendesk for picking those tools first.
  • Don’t have a domain to point at any of this yet: see Cloudflare Registrar vs Namecheap vs Porkbun for picking a registrar before either reverse proxy needs a domain to issue a certificate for.
  • Putting a self-hosted Listmonk instance behind HTTPS: see Listmonk vs Mailchimp vs Brevo — either proxy works the same way as any other self-hosted app here.

Bottom line

Both are solid, actively used choices for fronting a self-hosted app cluster with HTTPS — there’s no wrong pick for a homelab or small office. Caddy wins on simplicity-once-you-know-it and reproducibility; NPM wins on approachability for a first-timer who doesn’t want to learn reverse-proxy config syntax at all.