What is nginx-proxy?
A guest article from our maintainer Nicholas Duchon.
© linuxhandbook.com
What is nginx-proxy (and why pair it with acme-companion + ZeroSSL)?
If you run multiple web apps on one server, ingress and TLS management can become painful fast.
That’s where nginx-proxy and acme-companion come in.
nginx-proxy automatically routes traffic to Docker containers based on hostnames.
acme-companion automatically requests and renews TLS certificates for those hostnames via the ACME protocol. Using this protocol, certificates can be issued and renewed without manual work.
ZeroSSL is one of the ACME-enabled Certificate Authorities supported by acme-companion, and provides additional features like a certificate management console and surface scan insights.
Example: api.example.com, app.example.com, admin.example.com all on one Docker host.
nginx-proxy handles reverse proxying of each domain to their specific app container(s) while acme-companion handles cert issuance for each vhost, both automatically.
In short: this stack gives you automatic reverse proxying + automatic HTTPS, with very little operational overhead.
Why do you need nginx-proxy with acme-companion?
Beyond basic virtual-host routing, the nginx-proxy + acme-companion stack is useful for several advanced but common deployment patterns:
-
Host-based + path-based routing from one entrypoint
-
Services exposing multiple ports (same app, different interfaces)
-
Certificates for domains not tied to container env vars
-
DNS-01 challenge for wildcard certificates
1) Host-based + path-based routing from one entrypoint
When one domain serves multiple backends, nginx-proxy can route by path in addition to host.
Example:
example.com/ → frontend container
example.com/api → API container
example.com/admin → admin container
This lets teams consolidate traffic behind one public endpoint while still keeping services isolated by container.
2) Services exposing multiple ports (same app, different interfaces)
Some apps expose more than one useful port (for example: web UI + admin UI + metrics endpoint).
With nginx-proxy multi-port support, you can route traffic to the right upstream port cleanly, instead of forcing one-port-only patterns.
This is especially useful for platform teams standardizing ingress for heterogeneous services.
3) Certificates for domains not tied to container env vars (standalone certs)
Not every certificate maps directly to a running app container.
Using acme-companion standalone certificates, you can define certificate requests through a mounted user config file and manage certs for external or non-standard targets.
Example use cases:
Certificate prepared before a service is deployed
Certificate for infrastructure endpoints not directly managed as app containers
Centralized certificate provisioning workflows
4) DNS-01 challenge for wildcard certificates
With DNS-01 ACME validation, acme-companion can request wildcard certificates (for example *. example.com), which is not possible with HTTP-01.
This is useful when:
You need one certificate covering many subdomains
Services are ephemeral and subdomains are created dynamically
You cannot (or do not want to) expose HTTP challenge endpoints for every service
How acme-companion works with ZeroSSL
At runtime, nginx-proxy watches Docker events and generates NGINX vhost config dynamically.
acme-companion integrates with that flow:
Detect containers that request TLS certificates (via environment variables).
Register an account with the chosen ACME CA if necessary.
Request certificates and validate domain ownership using the ACME challenge flow.
Store certificates in shared Docker volumes used by nginx-proxy.
Trigger reloads so nginx-proxy serves fresh certificates.
Run scheduled renewals automatically.
acme-companion makes HTTPS automation easy, and ZeroSSL enhances that experience with tools that help teams stay in control as they scale.
With the ZeroSSL management console , you can monitor certificates from one place instead of tracking them host by host.
With surface scan and protection insights , you can discover exposed domains and certificate-related risks that might otherwise be missed.
Together, this means you get the best of both worlds: hands-off ACME automation plus clear operational visibility for your team. If your infrastructure is growing, ZeroSSL helps keep certificate operations simple, visible, and easier to manage over time.
💡 Nginx-proxy vs. NGINX Reverse Proxy Docs
The official docs page teaches you how to manually configure plain nginx as a reverse proxy — writing proxy_pass, proxy_set_header, and other directives yourself into config files. It's a general-purpose how-to for any nginx deployment.
nginx-proxy (the GitHub project) is a Docker container that automates that exact process for Docker environments: it watches the Docker socket, detects containers with a VIRTUAL_HOST env var set, and auto-generates the same kind of nginx reverse-proxy config the docs describe — no manual file editing. It's typically paired with acme-companion for automatic Let's Encrypt SSL.
Bottom line: the docs are the manual/reference; nginx-proxy is a tool that automates applying that reference in Docker contexts.
Quick setup instructions
This is a streamlined example for a single proxied app using nginx-proxy and acme-companion.
-
Create a Docker network:
docker network create proxy -
Create the required Docker volumes:
docker volume create certs docker volume create html docker volume create acme -
Start nginx-proxy:
docker run -d \ --name nginx-proxy \ --publish 80:80 \ --publish 443:443 \ --volume /var/run/docker.sock:/tmp/docker.sock:ro \ --volume certs:/etc/nginx/certs:ro \ --volume html:/usr/share/nginx/html:ro \ --network proxy \ nginxproxy/nginx-proxy -
Start acme-companion:
docker run -d \ --name acme-companion \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume certs:/etc/nginx/certs \ --volume html:/usr/share/nginx/html \ --volume acme:/etc/acme.sh \ --network proxy \ --env [email protected] \ --env ACME_CA_URI=https://acme.zerossl.com/v2/DV90 \ nginxproxy/acme-companion -
Start an app container in the same Docker network as the proxy, with domain metadata passed to the
VIRTUAL_HOST(nginx-proxy) andLETSENCRYPT_HOST(acme-companion) environment variables:docker run -d \ --name my-app \ --network proxy \ --env VIRTUAL_HOST=app.example.com \ --env LETSENCRYPT_HOST=app.example.com \ my-app-image:x.y.z
Alternatively, you can use Docker Compose to define all three containers in a single file:
services:
nginx-proxy:
image: nginxproxy/nginx-proxy
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- certs:/etc/nginx/certs:ro
- html:/usr/share/nginx/html:ro
networks:
- proxy
acme-companion:
image: nginxproxy/acme-companion
container_name: acme-companion
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- certs:/etc/nginx/certs
- html:/usr/share/nginx/html
- acme:/etc/acme.sh
environment:
DEFAULT_EMAIL: [email protected]
ACME_CA_URI: https://acme.zerossl.com/v2/DV90
networks:
- proxy
my-app:
image: my-app-image:x.y.z
container_name: my-app
environment:
VIRTUAL_HOST: app.example.com
LETSENCRYPT_HOST: app.example.com
networks:
- proxy
networks:
proxy:
volumes:
certs:
html:
acme:
Point your DNS A/AAAA record for app.example.com to this server,
and acme-companion will handle certificate issuance and renewal
for this domain.