Files
docker-infrastructure/nginx-proxy-acme
2026-01-25 14:43:24 +00:00
..
2026-01-25 14:43:24 +00:00
2026-01-25 14:39:16 +00:00
2026-01-25 14:39:16 +00:00
2026-01-25 14:39:16 +00:00

nginx-proxy + acme-companion

Automated reverse proxy with Let's Encrypt SSL certificates.

Setup

  1. Create the data directories:

    sudo mkdir -p /srv/nginx-proxy-acme/{certs,vhost.d,html,conf.d,acme}
    
  2. Copy configuration files:

    sudo cp conf/conf.d/* /srv/nginx-proxy-acme/conf.d/
    sudo cp conf/vhost.d/* /srv/nginx-proxy-acme/vhost.d/
    
  3. Configure environment:

    cp .env.example .env
    # Edit .env with your email
    
  4. Start the proxy:

    docker compose up -d
    

Architecture

  • nginx-proxy: Reverse proxy with auto-discovery of Docker containers
  • acme-companion: Automatic Let's Encrypt certificate management
  • static-certs: Dummy container that triggers cert issuance for non-container backends

Security

All hosts receive these security features via vhost.d/default:

  • HSTS: Strict-Transport-Security header (1 year)
  • Security headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Referrer-Policy
  • Block exploits: WAF rules against SQL injection, XSS, file injection, spam, bad user agents

Access Control

Private Hosts (IP restricted)

Private hosts include vhost.d/private which restricts access to:

  • 192.168.1.0/24 (local network)
  • 172.16.0.0/12 (Docker networks)

To make a new host private, create a vhost.d file:

echo 'include /etc/nginx/vhost.d/private;' | sudo tee /srv/nginx-proxy-acme/vhost.d/myapp.kolpacksoftware.com

Public Hosts

Public hosts have no vhost.d file (only default applies). They get security headers and block-exploits but no IP restrictions.

Current public hosts:

  • chd.kolpacksoftware.com
  • linkding.kolpacksoftware.com
  • organizer.rmstsa.org
  • ridge-resources.org
  • rmstsa.org / www.rmstsa.org
  • share.kolpacksoftware.com
  • vikunja.kolpacksoftware.com

Adding a Proxied Service

Add these environment variables to any container:

services:
  myapp:
    image: myapp:latest
    environment:
      - VIRTUAL_HOST=myapp.kolpacksoftware.com
      - VIRTUAL_PORT=8080
      - LETSENCRYPT_HOST=myapp.kolpacksoftware.com
    networks:
      - npm-network

networks:
  npm-network:
    external: true

Then if private, add the vhost.d file as shown above.

Static IP Backends

Services running on physical hosts or VMs (not Docker) are configured in conf.d/static-upstreams.conf:

Domain Backend
portainer.kolpacksoftware.com 172.17.0.1:9443
btt-cb1.kolpacksoftware.com 192.168.1.173:80
hats.kolpacksoftware.com 192.168.1.66:9999
pve-nas.kolpacksoftware.com 192.168.1.245:8006
unraid.kolpacksoftware.com 192.168.1.192:80

All static backends are private (IP restricted).

To add a new static backend:

  1. Add server block to conf.d/static-upstreams.conf
  2. Add domain to static-certs container's VIRTUAL_HOST and LETSENCRYPT_HOST in docker-compose.yaml
  3. Reload: docker exec nginx-proxy nginx -s reload

Directory Structure

/srv/nginx-proxy-acme/
├── acme/           # acme.sh state (auto-managed)
├── certs/          # SSL certificates (auto-managed)
├── conf.d/
│   ├── block-exploits.conf    # WAF rules
│   └── static-upstreams.conf  # Static IP backend server blocks
├── html/           # ACME challenge files (auto-managed)
└── vhost.d/
    ├── default                # Security headers + HSTS + block-exploits (all hosts)
    ├── private                # IP allowlist (private hosts include this)
    ├── docker-registry.*      # Special config (auth headers + private)
    └── <hostname>             # Per-host includes (usually just 'include private')

Reload Config

After changing vhost.d or conf.d files:

docker exec nginx-proxy nginx -s reload

Multiple Domains

For multiple domains on one cert:

environment:
  - VIRTUAL_HOST=example.com,www.example.com
  - LETSENCRYPT_HOST=example.com,www.example.com

Create vhost.d entries for each domain if private.