Tech Digest

Head to head

Caddy vs Nginx Proxy Manager

Both get you valid certificates without reading an ACME spec. The difference is whether your proxy configuration is something you can read, diff and restore.

Last reviewed 2 tools compared

Should you use Caddy or Nginx Proxy Manager?

Use Caddy unless you specifically want a web form, in which case Nginx Proxy Manager is the honest answer and no amount of arguing changes it. Caddy is 25 MB, one text file, and a site is three lines; NPM is 100 MB, a SQLite database and a UI that generates nginx config you must not hand-edit. The deciding factor for most people is not daily use, it is recovery: a Caddyfile is four lines in git, while NPM needs /data and /etc/letsencrypt restored together or nginx will not start.

This is not a performance comparison. Both put nginx-class throughput in front of services that will never saturate a home connection, and both get Let's Encrypt certificates without you learning ACME.

The axis is where configuration lives. Nginx Proxy Manager keeps it in a SQLite database and generates nginx config from it. Caddy keeps it in a file you wrote. Everything else in this comparison follows from that one difference: how you back it up, how you reproduce it, how you review a change, and what you do when something is wrong at a level the UI does not expose.

If you have never written a server block and do not want to start, NPM is the honest answer. The rest of this page is about what that choice costs later.

The specs are close, the ops load is not#

Memory is 25 MB against 100 MB idle, which matters on a 1 GB VPS and nowhere else. The row worth reading is the datastore, because it is the source of every difference below.

SpecificationCaddyNginx Proxy Manager
LicenceApache-2.0 (Permissive)MIT (Permissive)
Written inGoNode.js (Express) driving nginx/OpenResty
First release20152018
MaturityMatureMature
DatastoreFilesystem (certificates, keys and ACME account data in the data directory)SQLite by default; MariaDB/MySQL or PostgreSQL optional
Services to run11
Idle memory25 MB100 MB
Memory in use70 MB200 MB
Operational load1 / 5, Set and forget2 / 5, Light
IdentityNot applicableLocal accounts only
arm64 buildsYesYes
Default ports80, 443, 201980, 443, 81
Backup shapeFile copySQLite backup

NPM scores 2 on ops load rather than 1 for a specific reason: two coupled data directories have to be restored together, and the generated config is not something you can safely hand-edit.

Four lines against a form#

Here is a complete, working Caddy configuration for two services, including HTTPS, redirect and renewal:

caddyfile
jellyfin.example.com {
	reverse_proxy 192.168.1.20:8096
}

The NPM equivalent is: log in on port 81, Hosts, Proxy Hosts, Add Proxy Host, type the domain, type the IP, type the port, tick Websockets Support, SSL tab, Request a new certificate, Force SSL, Save. It takes about the same amount of time the first time and rather longer the tenth.

What you cannot do is show the NPM version to someone in a chat message, keep it in git, or diff it against last month. That is the trade, stated plainly.

The maintenance picture, as of now#

NPM's current release is 2.15.1, June 2026. Look at what recent releases contain: Debian base image bumps, OpenResty and certbot updates, CVE patches. Not new capability. There are roughly 890 open issues and the project is effectively one maintainer.

The v3 rewrite has an official status issue that has been open since June 2021, describing a Go backend, a React frontend, acme.sh in place of certbot and SQLite only. Five years later it has not shipped, and the released line is still the original Express and certbot stack.

That is not a scandal and it is not abandonment. Security updates arrive. But choose NPM for what it does today, because the feature set is not going to grow, and one concrete consequence has already landed: version 2.14 dropped armv7 images over a Node.js requirement, so a 32-bit Raspberry Pi is frozen at 2.13.7 and missing the OpenResty CVE fixes from 2.15.0. The fix is a 64-bit OS on the same board.

Caddy's release cadence is ordinary and its 2.11.x line has been shipping hardening work on the admin API. Different projects, different risk shapes: Caddy's is that you must rebuild custom plugin images on every upgrade, NPM's is that the thing you are running is in maintenance.

When the database gets confused#

NPM's state is /data/database.sqlite plus /etc/letsencrypt, and they reference each other. Proxy hosts point at certificates by numeric ID.

Restore /data from Tuesday and /etc/letsencrypt from Sunday and you get hosts referencing certificates that are not there. nginx then refuses to start, which means every site behind the proxy is down, not just the one that is inconsistent. The same class of failure appears when a volume is recreated, when you copy the data directory without preserving permissions, or when a certificate is deleted in the UI while a host still uses it.

The correct backup is both directories, taken together, with the container stopped or with sqlite3 database.sqlite ".backup out.db":

bash
docker compose stop npm
tar czf npm-$(date +%F).tar.gz ./data ./letsencrypt
docker compose start npm

/data/nginx and /data/logs can be excluded. The first is regenerated from the database on start; the second is disposable.

The Caddy equivalent is the Caddyfile, which is already in git, plus a volume of certificates that Caddy will simply re-issue if it is missing. There is no consistency requirement between two things, because there is only one thing. See Backups that actually restore for turning either into something that runs nightly.

Where NPM is genuinely better#

Three places, and they are not consolation prizes:

  • Someone else can add a proxy host. In a household or small office where more than one person needs to publish a service and nobody else has SSH, a web form is not a compromise, it is the requirement.
  • DNS-01 certificates without a rebuild. NPM bundles certbot DNS provider plugins and exposes them in a dropdown. Caddy ships zero DNS modules in its official binary and image, so wildcards or a port-80-unreachable host mean xcaddy build --with github.com/caddy-dns/cloudflare and a rebuild on every upgrade. For a wildcard-first setup, NPM is less work than Caddy. That surprises people.
  • TCP and UDP streams. NPM's Streams tab forwards raw ports. Caddy needs a third-party layer 4 module and a custom build for the same thing.

It also ships HTTP basic auth access lists, which is enough protection for a couple of internal tools without deploying Authelia and everything that comes with it.

The two NPM traps that cost people a weekend#

Port 81 with default credentials. First boot creates admin@example.com with the password changeme. These are documented publicly and there is no rate limiting in front of the login. People expose 81 "just for setup", get scanned within the hour, and hand a stranger a proxy that can be pointed at anything on their LAN. Bind 81 to a LAN address or keep it behind a VPN, and finish the forced credential change before DNS points anywhere. A security baseline for a home server covers the rest of the exposure question.

Websockets and HTTP/2 are off by default, per host. A new proxy host does not pass upgrade headers until you tick the box. The symptom is an app that loads perfectly and whose live updates never arrive: Home Assistant sitting at "connecting", Jellyfin sync play doing nothing. The nginx error log stays empty because nothing errored. Nothing in the UI hints at it.

Which one for your situation#

SituationUseWhy
You have never written an nginx server block and do not want toNginx Proxy ManagerThe form is the product, and it works
Your configuration should live in gitCaddyOne readable file, diffable, reviewable
More than one person needs to add hosts, without SSHNginx Proxy ManagerLocal accounts with per-user permissions
Wildcard certificate via DNS-01, no custom imagesNginx Proxy ManagerBundled certbot DNS plugins in a dropdown
1 GB VPS where 75 MB mattersCaddy25 MB idle against 100 MB
Forwarding raw TCP or UDP portsNginx Proxy ManagerStreams tab; Caddy needs a layer 4 plugin build
You plan to add Authelia or authentik laterCaddyforward_auth is one directive; NPM has no forward auth
32-bit Raspberry PiCaddyNPM armv7 images stopped at 2.13.7
Containers appear and disappear weeklyNeitherSee Caddy vs Traefik
You want to rebuild the whole host from a repoCaddyFour lines reproduce it; a SQLite database does not

What to do next#

If you are starting from nothing, write the Caddyfile first and see whether it bothers you. It is three lines, and if the answer is that you hate it, NPM is fifteen minutes away and you have lost nothing. Generate a first config with Reverse proxy generator, read Reverse proxy and TLS for the DNS and certificate decisions underneath either choice, and if you go with NPM, set up the two-directory backup on day one rather than after the first inconsistent restore. The rest of the category is at Networking.

Questions#

Is Nginx Proxy Manager still maintained in 2026?

Yes, but read the shape of it. 2.15.1 shipped in June 2026, and recent releases have been Debian base image, OpenResty and certbot updates plus CVE patches rather than new capability. There are roughly 890 open issues and it is effectively one maintainer. The v3 rewrite (Go backend, React frontend, acme.sh instead of certbot, SQLite only) has had an open status issue since June 2021 and has not landed. That is not abandonment. It does mean choosing NPM for its current feature set, not a future one.

What happens if NPM's database gets confused?

Proxy hosts reference certificates by numeric ID, so the failure is usually a mismatch between /data/database.sqlite and /etc/letsencrypt. Restore one without the other and you get hosts pointing at certificates that do not exist, and nginx refuses to start, which takes every site down at once rather than the one you broke. Back both directories up together and restore them together. /data/nginx is regenerated from the database on start, so it is not worth keeping.

Why does my app load through NPM but its live updates never arrive?

Websockets are a per-host toggle and it is off by default, as is HTTP/2 on the SSL tab. The request is proxied as plain HTTP, so the page loads and nothing errors, the nginx log stays empty, and only the socket-based features fail. Home Assistant, Jellyfin sync play and anything with live status hit this. Tick Websockets Support on the host and save. Caddy's reverse_proxy passes upgrade headers with no configuration.

Can Caddy do TCP or UDP forwarding like NPM's streams?

Not in the stock build. NPM has a Streams tab that forwards raw TCP and UDP ports, which is useful for game servers and SMTP relays, and Caddy needs a third-party layer 4 module and therefore a custom build to match it. If stream forwarding is a requirement, that is a genuine point for NPM, or an argument for leaving those ports to the firewall entirely.

Which one is easier to move to a new machine?

Caddy, by a wide margin. Copy the Caddyfile and the /data volume, start the container, and certificates are already there; even without the volume it re-issues them on first request. Moving NPM means stopping the container, copying both /data and /etc/letsencrypt with permissions intact, and starting the new one before DNS moves. If migration and reproducibility matter to you, that difference is the whole comparison.

Does NPM support single sign-on?

No. It has local accounts with per-user permissions and an audit log, plus HTTP basic auth access lists you can attach to a host. There is no OIDC, no LDAP and no forward auth, and none is planned in the v2 line. If you want Authelia or authentik in front of your apps, you need Caddy's forward_auth directive or a Traefik ForwardAuth middleware instead.

Sources#

Published . Last reviewed . Found something out of date? Tell us and we will fix it and log the change.