Tutorials

Reverse Proxy and HTTPS, Set Up Once

Chapter 8's Nginx Proxy Manager from empty folder to a trusted padlock: the shared proxy network, a DNS-only API token, one wildcard certificate for every service, and a way to test it all before Pi-hole exists.

intermediate ⏱ 45 min Be Your Own Cloud · Chapter 8 nginx proxy managerhttpslets encryptdnshomelab

Chapter 8 of Be Your Own Cloud explains why everything after it sits behind a reverse proxy, and how a service that’s never on the internet still gets a real HTTPS certificate. Every service tutorial on this site assumes it’s done. This page is the setup in order, with one certificate that covers every service, a way to test before Pi-hole is running, and how to add each service from here on.

Before you start

  • A server with Docker, from Installing Ubuntu Server and Docker and Compose.
  • A domain you own. The examples use home.yourdomain.com, as the book does. Nothing under it will ever be public.
  • That domain’s DNS at a provider Nginx Proxy Manager supports. Chapter 8 suggests Cloudflare, which is free for this; this page uses it. Moving the DNS means changing the domain’s nameservers at the registrar where you bought it — Cloudflare shows you the two to use when you add the domain.

1. The folder, the network, the proxy

sudo mkdir -p /opt/stacks/npm
cd /opt/stacks/npm

The compose file, as printed:

File /opt/stacks/npm/docker-compose.yml
services:
  npm:
    image: 'jc21/nginx-proxy-manager:2.16.0'
    container_name: npm
    restart: unless-stopped
    ports:
      - '80:80'
      - '443:443'
      - '81:81'   # admin UI
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - proxy

networks:
  proxy:
    external: true
Exactly as printed in Be Your Own Cloud, Chapter 8 , p. 37 · all of Chapter 8's code

The shared network, once, before the first start:

Run Terminal, on the server — once
docker network create proxy
Exactly as printed in Be Your Own Cloud, Chapter 8 , p. 37 · all of Chapter 8's code
docker compose up -d
docker compose logs -f npm

The first start takes a minute or two while it sets itself up. When the log settles, press Ctrl+C.

2. The admin account — straight away

Open http://192.168.1.50:81. You get a Welcome! page that creates the admin account. As Chapter 8 says, do it now: until it exists, anyone on your network who opens port 81 first gets to create it. Use a long random password from your password manager.

Then turn on two-factor: open the user menu at the top right, choose Two-Factor Auth, and scan the code with your authenticator app. This panel controls where every service’s traffic goes, so it deserves the same protection as your email.

3. A Cloudflare token that can only edit DNS

In Cloudflare, go to My Profile → API Tokens → Create Token and use the Edit zone DNS template. Under Zone Resources, choose Include → Specific zone → your domain. Continue, then Create Token, and copy it — Cloudflare shows it once.

That’s the narrowest token that works: it can change DNS records on one domain and nothing else. Chapter 8’s troubleshooting box comes back to this — a read-only token, or one for the wrong domain, is the usual reason a certificate request fails.

4. One certificate for every service

Chapter 8 gets its certificates through the DNS challenge. Because of that, you can ask for a wildcard certificate — *.home.yourdomain.com — once, and pick it for every service from then on. That’s what “your DNS-challenge certificate from Chapter 8” means in this site’s service tutorials.

In Nginx Proxy Manager, open Certificates, choose Add Certificate, then Let’s Encrypt via DNS:

FieldValue
Domain Names*.home.yourdomain.com
DNS ProviderCloudflare
Credentials File Contentreplace the example token with yours: dns_cloudflare_api_token= followed by the token
Propagation Secondsleave empty; if the request times out, try 60
Key Typeleave the default

Save, and give it a minute or two. Let’s Encrypt checks a temporary DNS record that Nginx Proxy Manager creates through the token, then removes. When it’s done, the certificate appears in the list with an Expires date.

Nginx Proxy Manager warns that the credentials are kept in plain text in its database. That’s the other reason for a token that can only touch DNS on one domain. The database lives in /opt/stacks/npm/data, which Chapter 18’s backup takes in full, into an encrypted repository.

5. Your first proxy host: the admin page itself

Something to test with, and a nicer address for the admin page. Hosts → Proxy Hosts → Add Proxy Host:

TabSettingValue
DetailsDomain Namesnpm.home.yourdomain.com
DetailsSchemehttp
DetailsForward Hostname / IPnpm
DetailsForward Port81
DetailsBlock Common Exploitson
SSLSSL Certificateyour *.home.yourdomain.com certificate
SSLForce SSL, HTTP/2 Supporton

npm is the container’s name. It’s on the proxy network, so Docker resolves the name there — the same way every service that joins that network is reached.

6. Test it before Pi-hole exists

Chapter 8 makes these names work at home with local DNS records in Pi-hole — but that’s Chapter 10. You don’t have to wait to know the proxy and certificate work. From a laptop on your network:

curl -sI --resolve npm.home.yourdomain.com:443:192.168.1.50 https://npm.home.yourdomain.com

--resolve tells curl the address for this one request, so no DNS is involved. Windows 10 and 11 have curl too — type curl.exe in PowerShell. An HTTP/2 200 back, with no certificate error, means the proxy, the certificate and the forward all work.

To open it in a browser before Pi-hole is running, you can add a temporary line to the laptop’s hosts file (/etc/hosts on Linux and Mac, C:\Windows\System32\drivers\etc\hosts on Windows), and take it out once Pi-hole answers instead:

192.168.1.50  npm.home.yourdomain.com

7. When Pi-hole is running

Chapter 8’s records, added in Pi-hole under Settings → Local DNS Records:

Web UI Pi-hole → Settings → Local DNS Records

One record per subdomain, all pointing at the server. Added once Chapter 10's Pi-hole is running.

vault.home.yourdomain.com  ->  192.168.1.50
nextcloud.home.yourdomain.com  ->  192.168.1.50
# Same IP for all of them -- Nginx Proxy Manager on that one machine
# is what routes each subdomain to the right container behind it.
Exactly as printed in Be Your Own Cloud, Chapter 8 , p. 38 · all of Chapter 8's code

Or one wildcard record that covers them all — see One record for every subdomain in the Pi-hole tutorial.

8. Adding each service from here on

Every service follows one of two patterns. If its compose file joins the proxy network, forward to its container name — it doesn’t publish a port on the server at all. If it publishes a port on the server instead, forward to the server’s IP and that port.

ServiceForward Hostname / IPForward PortSettings
Vaultwardenvaultwarden80Vaultwarden
Nextcloud AIO192.168.1.5011000Nextcloud AIO
Immich192.168.1.502283Immich
Jellyfin192.168.1.508096Jellyfin
The Sanitisersanitizer5000Local AI
Open WebUI192.168.1.508090Local AI

Each tutorial’s table has the rest — Websockets, upload limits, the Advanced tab (the gear icon at the right-hand end of the proxy host’s tabs) — for that service.

Checks worth knowing

Who’s on the shared network — anything the proxy forwards to by name has to be listed:

docker network inspect proxy --format '{{range .Containers}}{{.Name}} {{end}}'

What the server is listening on — you should see 80, 443 and 81 from the proxy, plus any ports your services publish:

sudo ss -tlnp

Two problems Chapter 8’s troubleshooting box doesn’t cover:

  • 502 Bad Gateway. The proxy can’t reach what you forwarded to. The forward hostname is a container that isn’t on proxy (check the command above), or the port is wrong. Forwarding by container name means the port the app listens on inside its container — 80 for Vaultwarden.
  • A certificate warning on one name only. A wildcard covers one level: immich.home.yourdomain.com, but not photos.immich.home.yourdomain.com. Keep to one level under home.

Renewals and updates

Let’s Encrypt certificates are short-lived, and Nginx Proxy Manager renews them itself, through the same token. Look at the Expires date on the Certificates page a month or so from now: if it has moved on, renewal works. If it hasn’t, Renew Certificate in the certificate’s menu shows the error.

The book pins jc21/nginx-proxy-manager:2.16.0. Before you change that, read the release notes for every version in between — recent ones have updated Certbot, which can affect DNS-challenge renewals (Chapter 27) — and make sure last night’s backup ran (Chapter 18).

Official documentation

Stuck on a step? Get in touch.