From d84e412532b60e8798dbd0449ffd01cd68e70f0a Mon Sep 17 00:00:00 2001 From: poprhythm Date: Sun, 25 Jan 2026 14:39:16 +0000 Subject: [PATCH] Add nginx-proxy-acme --- nginx-proxy-acme/.env.example | 2 + nginx-proxy-acme/README.md | 142 +++++++++++++++++++++++++++ nginx-proxy-acme/docker-compose.yaml | 48 +++++++++ 3 files changed, 192 insertions(+) create mode 100644 nginx-proxy-acme/.env.example create mode 100644 nginx-proxy-acme/README.md create mode 100644 nginx-proxy-acme/docker-compose.yaml diff --git a/nginx-proxy-acme/.env.example b/nginx-proxy-acme/.env.example new file mode 100644 index 0000000..59f38ca --- /dev/null +++ b/nginx-proxy-acme/.env.example @@ -0,0 +1,2 @@ +# Let's Encrypt notification email +LETSENCRYPT_EMAIL=your-email@example.com diff --git a/nginx-proxy-acme/README.md b/nginx-proxy-acme/README.md new file mode 100644 index 0000000..b32f487 --- /dev/null +++ b/nginx-proxy-acme/README.md @@ -0,0 +1,142 @@ +# nginx-proxy + acme-companion + +Automated reverse proxy with Let's Encrypt SSL certificates. + +## Setup + +1. Create the data directories: + ```bash + sudo mkdir -p /srv/nginx-proxy-acme/{certs,vhost.d,html,conf.d,acme} + ``` + +2. Copy configuration files: + ```bash + 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: + ```bash + cp .env.example .env + # Edit .env with your email + ``` + +4. Start the proxy: + ```bash + 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: +```bash +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: + +```yaml +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) + └── # Per-host includes (usually just 'include private') +``` + +## Reload Config + +After changing vhost.d or conf.d files: +```bash +docker exec nginx-proxy nginx -s reload +``` + +## Multiple Domains + +For multiple domains on one cert: +```yaml +environment: + - VIRTUAL_HOST=example.com,www.example.com + - LETSENCRYPT_HOST=example.com,www.example.com +``` + +Create vhost.d entries for each domain if private. diff --git a/nginx-proxy-acme/docker-compose.yaml b/nginx-proxy-acme/docker-compose.yaml new file mode 100644 index 0000000..c03105a --- /dev/null +++ b/nginx-proxy-acme/docker-compose.yaml @@ -0,0 +1,48 @@ +services: + nginx-proxy: + image: nginxproxy/nginx-proxy:1.6 + container_name: nginx-proxy + restart: unless-stopped + ports: + - '80:80' + - '443:443' + volumes: + - /srv/nginx-proxy-acme/certs:/etc/nginx/certs:ro + - /srv/nginx-proxy-acme/vhost.d:/etc/nginx/vhost.d + - /srv/nginx-proxy-acme/html:/usr/share/nginx/html + - /srv/nginx-proxy-acme/conf.d/static-upstreams.conf:/etc/nginx/conf.d/static-upstreams.conf:ro + - /srv/nginx-proxy-acme/conf.d/block-exploits.conf:/etc/nginx/conf.d/block-exploits.conf:ro + - /var/run/docker.sock:/tmp/docker.sock:ro + environment: + - TRUST_DOWNSTREAM_PROXY=false + + acme-companion: + image: nginxproxy/acme-companion:2.4 + container_name: acme-companion + restart: unless-stopped + volumes_from: + - nginx-proxy + volumes: + - /srv/nginx-proxy-acme/certs:/etc/nginx/certs:rw + - /srv/nginx-proxy-acme/acme:/etc/acme.sh + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - DEFAULT_EMAIL=${LETSENCRYPT_EMAIL} + depends_on: + - nginx-proxy + + # Dummy container to trigger certificate issuance for static IP backends + # This container does nothing but hold env vars for acme-companion to detect + static-certs: + image: alpine:3.19 + container_name: static-certs + restart: unless-stopped + command: ["sleep", "infinity"] + environment: + - VIRTUAL_HOST=portainer.kolpacksoftware.com,btt-cb1.kolpacksoftware.com,hats.kolpacksoftware.com,pve-nas.kolpacksoftware.com,unraid.kolpacksoftware.com + - LETSENCRYPT_HOST=portainer.kolpacksoftware.com,btt-cb1.kolpacksoftware.com,hats.kolpacksoftware.com,pve-nas.kolpacksoftware.com,unraid.kolpacksoftware.com + +networks: + default: + name: npm-network + external: true