unsubbed.co

nginx-proxy

Automated Nginx reverse proxy for Docker containers using docker-gen for dynamic configuration.

Automated nginx configuration for Docker environments, honestly reviewed. No marketing fluff, just what happens when you hand VIRTUAL_HOST to a container and walk away.

TL;DR

  • What it is: A Docker container that runs nginx and docker-gen together, auto-generating reverse proxy configs as containers start and stop — no manual nginx.conf editing required [README].
  • Who it’s for: Developers and self-hosters running multiple Docker services on a single server who want traffic routed by domain name without touching nginx configuration files by hand.
  • Cost savings: nginx-proxy itself is free (MIT). The alternative — manually managing nginx or paying for a cloud load balancer — costs either your time or real money. AWS ALB starts at ~$16/mo plus data processing fees; Cloudflare Tunnels’ free tier has rate limits and routes traffic through Cloudflare’s network.
  • Key strength: The simplest possible interface — one environment variable on your container and nginx-proxy handles the rest. 19,778 GitHub stars and 10+ years of production history back up the claim that it works [README].
  • Key weakness: Pure CLI/config tool with no web GUI, no built-in Let’s Encrypt integration in the base image, and a learning curve around Docker networking that catches newcomers. If you want a dashboard, you want nginx-proxy-manager (a related but separate project).

What is nginx-proxy

nginx-proxy is a single Docker container that solves a real problem: you’re running five, ten, or twenty Dockerized services on one server and you want plex.yourdomain.com, gitea.yourdomain.com, and nextcloud.yourdomain.com to each route to the right container over HTTPS — without touching a config file every time you deploy something new.

The mechanism is straightforward. The container runs two processes: nginx (the actual web server and proxy) and docker-gen (a Go tool that watches the Docker socket for container events). When you start a new container with the env var VIRTUAL_HOST=foo.bar.com, docker-gen detects the event, renders a new nginx config from a template, and tells nginx to reload. When you stop the container, it does the same in reverse [README].

The whole interaction looks like this in practice:

# Start nginx-proxy once
docker run --detach \
    --name nginx-proxy \
    --publish 80:80 \
    --volume /var/run/docker.sock:/tmp/docker.sock:ro \
    nginxproxy/nginx-proxy:1.10

# Then any container you want proxied
docker run --detach \
    --env VIRTUAL_HOST=foo.bar.com \
    your-app-image

That’s the full setup for HTTP routing. No nginx.conf edits. No service restarts. No config file to maintain [README].

The project has been around since Jason Wilder’s 2014 blog post “Automated Nginx Reverse Proxy for Docker” — a post that’s still linked from the README a decade later [README]. It sits at 19,778 stars and 3,100+ forks across 148 contributors, which for an infrastructure utility is strong signal that it solves a common problem well.

One important clarification upfront: nginx-proxy and nginx-proxy-manager (NPM) are different projects. NPM is a separate GUI-based tool built on top of nginx. Most search results — and most of the third-party reviews available — discuss NPM. This review covers the original nginx-proxy, the config-file-free Docker automation layer.


Why people choose it

The r/selfhosted community has written extensively on reverse proxy options [2]. The consistent pattern across comparisons: tool selection depends heavily on how comfortable you are with config files.

nginx-proxy wins on automation and simplicity for Docker-centric setups. The label/env-var model — where each container declares its own routing via environment variables — integrates cleanly with docker-compose files. You add one line to your service definition and routing happens. This matches the mental model of infrastructure-as-code practitioners who want their docker-compose.yml to be the single source of truth for deployment.

The r/selfhosted comparison [2] frames the reverse proxy landscape with reasonable clarity:

“Caddy: As easy as it could possibly get… Traefik: Probably the most powerful and versatile… NGINX: Old reliable. It’s only this far down the list because I prefer Traefik over vanilla NGINX for more complex use cases.”

nginx-proxy inherits nginx’s “old reliable” reputation while adding the automation layer that raw nginx lacks. For Docker environments, it’s the middle path: more automated than manually managing nginx, less opinionated and GUI-dependent than NPM, less feature-rich than Traefik.

Versus Traefik, the common alternative for Docker setups: Traefik uses Docker labels (similar concept), has a built-in dashboard, handles Let’s Encrypt natively, and offers more advanced routing features. The trade-off is complexity — Traefik’s documentation is extensive and the mental model around middlewares and providers has a real learning curve [2]. nginx-proxy is simpler if your needs are simpler.

Versus Caddy: Caddy wins on TLS automation and a readable config format (Caddyfile), but its Docker integration isn’t as seamless as nginx-proxy’s pure env-var model [2].

Versus nginx-proxy-manager (NPM): NPM adds a web GUI and Let’s Encrypt certificate automation on top of nginx. The XDA review [3] calls NPM “perfect for the home lab” specifically because of that GUI. The r/selfhosted comparison is blunter: “Unreliable… Don’t let its shiny user-friendly frontend fool you, as underneath lies a trove of deceit that will inevitably lead you down a rabbit hole of stale issues” [2]. nginx-proxy, being closer to bare nginx, carries less of that baggage — but also less hand-holding.


Features

Based on the README and Docker Hub documentation [README]:

Core routing:

  • Automatic nginx config generation via docker-gen when containers start or stop
  • VIRTUAL_HOST env var for domain routing — supports multiple hostnames per container (comma-separated)
  • Virtual ports: override the default port nginx-proxy proxies to via VIRTUAL_PORT
  • Path-based routing with VIRTUAL_PATH and VIRTUAL_DEST
  • WebSocket support (automatic for most configs)
  • HTTP/2 support

SSL/TLS:

  • HTTPS support via VIRTUAL_PROTO=https for upstream containers
  • Integration with letsencrypt-nginx-proxy-companion (now acme-companion) for automatic certificate management — this is a separate sidecar container, not built in
  • Support for custom certificates mounted into the container
  • HTTP to HTTPS redirect support

Networking:

  • Multi-network support — proxied containers must share at least one Docker network with nginx-proxy [README]
  • network_internal.conf for restricting access to internal networks
  • Support for upstream load balancing across multiple containers with the same VIRTUAL_HOST

Configuration:

  • Debian (nginx:mainline) and Alpine variants [README]
  • Environment-variable overrides for nginx settings
  • Custom nginx config snippets via mounted volumes
  • Separate containers mode: run nginx and docker-gen as separate containers for better separation of concerns [docker-compose-separate-containers.yml in repo]

What it doesn’t include:

  • Web GUI (use NPM or Traefik for that)
  • Built-in Let’s Encrypt (requires acme-companion sidecar)
  • Access control / authentication layers (handle upstream or with a separate auth proxy)
  • Dashboard or metrics UI

Pricing: SaaS vs self-hosted math

nginx-proxy is MIT-licensed and free. There’s no cloud version, no paid tier, no usage caps [README].

The relevant cost comparison is against managed alternatives:

Cloud load balancers (the self-hosting alternative):

  • AWS Application Load Balancer: ~$16.20/mo base + $0.008 per LCU-hour — for a small multi-service setup, expect $20–40/mo
  • DigitalOcean Load Balancer: $12/mo minimum
  • Cloudflare Tunnels: free tier available, but routes traffic through Cloudflare (data sovereignty tradeoff), and the zero-trust features cost $7+/mo per user at scale

Self-hosted nginx-proxy:

  • Software: $0
  • VPS to run everything: $5–10/mo (Hetzner, Contabo, Netcup)
  • Domain name: ~$10–15/yr
  • Time investment: one afternoon to set up, minimal ongoing maintenance

For a non-technical founder running 5–10 services, the math is straightforward. The managed alternatives cost $15–40/mo ongoing. nginx-proxy costs your VPS bill, which you’re paying anyway.

The hidden cost is setup time. Getting Docker networking right (shared networks, expose directives) has tripped up enough users that it’s worth budgeting 2–4 hours for a first deployment, not 30 minutes [README][2].


Deployment reality check

The basic Docker run command in the README works. The nuances are in the networking.

What you actually need:

  • Docker and docker-compose on a Linux server
  • Containers must EXPOSE their port and share a Docker network with nginx-proxy [README]
  • DNS pointing your domains at the server’s IP
  • The acme-companion sidecar if you want automatic Let’s Encrypt certificates (separate setup)

Where things go wrong:

The most common failure mode is network isolation. The README is explicit: “if you don’t pass the —net flag when your nginx-proxy container is created, it will only be attached to the default bridge network. This means that it will not be able to connect to containers on networks other than bridge” [README]. In docker-compose setups with custom networks, every service that needs proxying must be on a network that nginx-proxy is also attached to. This isn’t obvious the first time.

The latest tag warning in the README is worth heeding: “It is not recommended to use the latest… or alpine tag for production setups… using them will probably put your nginx-proxy setup at risk of experiencing uncontrolled updates to non backward compatible versions” [README]. Pin to a specific version like :1.10.

The Dave Isaksson write-up [4] covers a related NPM setup but illustrates the broader pattern: setting up DNS wildcard records plus Let’s Encrypt DNS-01 challenges adds meaningful complexity beyond the basic HTTP routing. If you want internal services with valid TLS (not just on public-facing ports), factor in a Cloudflare API key and additional configuration for the acme-companion.

Realistic time estimates:

  • HTTP routing for public-facing services, no TLS: 30–60 minutes
  • HTTPS with Let’s Encrypt via acme-companion: 2–3 hours including DNS propagation
  • Multi-network docker-compose setup with multiple services: half a day if you’re debugging network isolation for the first time

Pros and Cons

Pros

  • Zero-config routing for Docker. One env var per container, nginx-proxy handles the rest. No template files, no manual reload [README].
  • MIT license. Use it, fork it, embed it, resell it — no commercial agreement required [README].
  • Proven and stable. 10+ years in production, 19,778 stars, 148 contributors, 33 releases [README]. This is not a side project that gets abandoned.
  • Slim footprint. Alpine variant is a small image. It does one job and does it well.
  • Close to nginx. You’re not abstracted away from the underlying system. nginx configs are on disk, readable, tweakable. When something goes wrong, you can read the generated config and understand what happened.
  • Separate containers mode. Run nginx and docker-gen as separate containers for better process isolation — useful for more advanced setups [README].
  • Active maintenance. 1.10.1 released March 2026, with 33 total releases [website data].

Cons

  • No web GUI. Everything is env vars, config files, and Docker commands. Non-technical users will struggle [2][3].
  • No built-in Let’s Encrypt. Requires the separate acme-companion container, adding setup complexity. NPM and Traefik handle this natively [README][4].
  • Docker networking gotchas. Shared network requirements catch newcomers repeatedly. The “why isn’t it routing?” troubleshooting loop is a real time sink [README][2].
  • No authentication layer. No built-in basic auth, OAuth, or access control. Add-ons like oauth2-proxy or Authentik are separate integrations.
  • No dashboard. You can’t see traffic, errors, or routing status without reading logs or installing something else.
  • Configuration is implicit. Your routing rules live in environment variables scattered across docker-compose files. Hard to audit at a glance compared to a single Caddyfile or Traefik config.

Who should use this / who shouldn’t

Use nginx-proxy if:

  • You’re a developer or technically comfortable sysadmin running multiple Docker services on one server.
  • You work primarily in docker-compose and want routing to be part of your compose files rather than a separate config to maintain.
  • You value staying close to nginx — you want to understand what’s happening and be able to debug it.
  • You need a lightweight, proven solution with no GUI overhead.
  • You want MIT licensing with no commercial restrictions.

Skip it (use nginx-proxy-manager) if:

  • You want a web GUI to manage proxy routes without touching the terminal.
  • You want Let’s Encrypt certificate management built in without a separate sidecar container.
  • You’re non-technical and were directed here by a tutorial — NPM has more beginner documentation [3][4].

Skip it (use Traefik) if:

  • You need advanced routing logic: middlewares, rate limiting, circuit breakers, traffic splitting.
  • You want a built-in dashboard to visualize routing and health.
  • You’re already using Kubernetes or need multi-provider support [2].

Skip it (use Caddy) if:

  • You’re not Docker-centric and want the simplest possible config file with automatic TLS.
  • Your use case is a handful of services on one machine and you want the entire setup in one Caddyfile [2].

Alternatives worth considering

  • Traefik — Docker-native like nginx-proxy, but adds a dashboard, native Let’s Encrypt, and middleware support. More complex. For teams that need power features [2].
  • Caddy — Config-file based (Caddyfile), excellent automatic TLS, very readable. Excellent for simpler setups not heavily Docker-centric [2].
  • nginx-proxy-manager (NPM) — Adds a web GUI on top of nginx. More beginner-friendly but has reliability complaints in the r/selfhosted community [2]. Different project from nginx-proxy.
  • Zoraxy — Newer GUI-based option, described as the best GUI reverse proxy for home use by the r/selfhosted reviewer, but missing some features (like DNS challenge SSL) [2].
  • HAProxy — Enterprise-grade load balancer. High-performance, complex configuration. Overkill for most self-hosters.
  • Cloudflare Tunnels — Zero-config, free tier available, but your traffic routes through Cloudflare’s network. Not self-hosted in any meaningful sense.

Bottom line

nginx-proxy does exactly what it says: it automates nginx configuration for Docker environments using environment variables. It’s been doing this since 2014, it has 19,778 GitHub stars backing it up, and the MIT license means there are no commercial strings. The trade-off is real — there’s no GUI, no built-in Let’s Encrypt, and Docker networking requires you to understand what you’re doing. For a non-technical founder who wants to click through a web interface, nginx-proxy-manager or Traefik will be a better fit. For a developer who lives in docker-compose files and wants routing to be a one-line declaration per service, nginx-proxy is the kind of tool that gets out of your way and keeps working.

If the networking setup is the blocker, that’s the kind of one-time infrastructure work that upready.dev handles for clients. One afternoon, done, and you never think about reverse proxy configuration again.


Sources

  1. Asbed B, Medium“What I Learned Deploying NGINX Proxy Manager” (Feb 28, 2025). https://medium.com/@asbedb/what-i-learned-deploying-nginx-proxy-manager-f47271d230d6
  2. r/selfhosted, dipplersdelight“My very biased personal review of several self-hosted reverse proxy solutions for home use”. https://www.reddit.com/r/selfhosted/comments/1cu2dow/my_very_biased_personal_review_of_several/
  3. Joe Rice-Jones, XDA Developers“5 reasons Nginx Proxy Manager is perfect for the home lab” (May 9, 2025). https://www.xda-developers.com/nginx-proxy-manager-best-reverse-proxy/
  4. David Isaksson, davidisaksson.dev“Setting up a local reverse proxy with Nginx Proxy Manager and Let’s Encrypt”. https://davidisaksson.dev/posts/reverse-proxy/

Primary sources: