Reverse Proxy & HTTPS

← Self-Hosting · Foundations 3 of 8 · Replaces: remembering IP:port numbers

What You'll Build

Right now each app lives at an address like http://192.168.1.50:2283. A reverse proxy is a single front door on ports 80/443 that looks at the name you typed and forwards you to the right app:

photos.home.example.com  ─┐
vault.home.example.com   ─┼─►  Nginx Proxy Manager (:443)  ─►  Immich :2283
jellyfin.home.example.com ─┘                               ─►  Vaultwarden :8082
                                                            ─►  Jellyfin :8096

It also handles HTTPS certificates from Let's Encrypt automatically. Some apps — Vaultwarden, Nextcloud, and phone camera uploads — need HTTPS to work properly.

Ports: 80, 443, and 81 (admin) · Time: 45 minutes · Difficulty: Medium

What You Need

Step 1: Private or Public?

Private (recommended)Public
Who can reach your appsDevices at home, and your devices on TailscaleAnyone on the internet (behind each app's login)
Router port forwardingNoneForward 80 and 443 to the server
How certificates are issuedDNS challenge (Cloudflare API token)HTTP challenge
RiskLowHigher — every app must be kept updated

This guide builds the private setup. A box at the end covers the public differences.

Step 2: Start Nginx Proxy Manager

mkdir -p ~/stacks/npm && cd ~/stacks/npm
nano compose.yaml
services:
  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: npm
    restart: unless-stopped
    ports:
      - "80:80"      # HTTP  (redirects to HTTPS)
      - "443:443"    # HTTPS (all your apps come in here)
      - "81:81"      # the admin web page
    environment:
      TZ: "America/New_York"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
docker compose up -d
sudo ufw allow 80,443/tcp     # let browsers on your network reach it

Open http://192.168.1.50:81. Current versions show a first-run screen where you create your admin account. (Older versions used admin@example.com / changeme — if you see a login page instead, use those and change them immediately.)

Step 3: Point Your Names at the Server

For a private setup, a wildcard DNS record points every *.home.example.com name at your server's local IP. It only works on your home network (and over Tailscale with subnet routing), which is exactly what we want.

  1. In Cloudflare: your domain → DNS → Records → Add record.
  2. Type A, Name *.home, IPv4 address 192.168.1.50, Proxy status DNS only (grey cloud) → Save.

Some routers block DNS answers that point to local IPs ("DNS rebind protection"). If names don't resolve at home, add the same wildcard as a local DNS record in Pi-hole or AdGuard Home instead.

Step 4: Get a Wildcard Certificate (DNS Challenge)

  1. In Cloudflare: My Profile → API Tokens → Create Token → template Edit zone DNS → Zone Resources: your domain → Create. Copy the token (it's shown once).
  2. In Nginx Proxy Manager: SSL Certificates → Add SSL Certificate → Let's Encrypt.
  3. Domain names: *.home.example.com and home.example.com.
  4. Turn on Use a DNS Challenge, provider Cloudflare, and replace the sample token in the credentials box with yours: dns_cloudflare_api_token=YOUR_TOKEN
  5. Agree to the terms → Save. After a minute or two the certificate appears with a green status.

Let's Encrypt proves you own the domain by checking a DNS record, so your server never has to be reachable from the internet.

Step 5: Add Your First Proxy Host

Let's put Portainer behind the proxy as a test:

  1. Hosts → Proxy Hosts → Add Proxy Host.
  2. Domain Names: portainer.home.example.com
  3. Scheme: https (Portainer itself uses HTTPS) · Forward Hostname/IP: 192.168.1.50 · Forward Port: 9443
  4. Tick Block Common Exploits and Websockets Support.
  5. SSL tab: choose your *.home.example.com certificate, tick Force SSL and HTTP/2 Support → Save.

Open https://portainer.home.example.com — padlock, no warning. Every app guide on this site ends with the name and port to add here.

SettingMost appsExceptions
SchemehttpPortainer (9443), Nextcloud AIO interface (8080) use https
WebsocketsAlways tick it—
Big uploads—Immich / Nextcloud / Paperless: in the Advanced tab add client_max_body_size 0;

Going Public Instead (Optional)

Only do this if you need friends or family to reach an app without Tailscale. Anything you publish is attacked by bots within hours; keep those apps updated and use strong passwords with two-factor authentication.

Troubleshooting

What's Next?

Reach all of this from your phone, anywhere, without opening ports:

Foundations 4: Remote Access with Tailscale or WireGuard →