certbot cannot issue for internal hostnames (*.nas.local), which made nginx-enabled services with internal names fail on deploy. Add a <service>_nginx_letsencrypt flag (default false) and computed <service>_nginx_ssl_certificate/_key paths: when off the vhost uses the shared self-signed cert (nginx_default_ssl_cert), when on it uses the Let's Encrypt lineage. Gate the certbot include on the flag and apply the pattern to all service roles (deluge, fdroid, gitea, grafana, immich, metabase, ntfy, prowlarr, radarr, seerr, static_web, uptime_kuma).
158 lines
5.0 KiB
Markdown
158 lines
5.0 KiB
Markdown
# Nginx Role
|
|
|
|
Installs and configures Nginx as a reverse proxy for web applications with modular vhost management.
|
|
|
|
## Features
|
|
|
|
- Modular vhost configuration via `/etc/nginx/conf.d/`
|
|
- Zero-downtime reloads
|
|
- Configurable logging backend (journald or traditional files)
|
|
- Automatic logrotate for file-based logging
|
|
- SSL/TLS configuration
|
|
- **Native ACME/Let's Encrypt support** (Nginx 1.25.0+)
|
|
- **Transparent proxy forwarding** (HTTP/HTTPS to other hosts)
|
|
- **Catch-all `default_server`** that rejects unknown SNI/Host with `444`
|
|
|
|
## Catch-all default_server
|
|
|
|
A `00-default.conf` vhost is deployed and marked `default_server` on both
|
|
ports 80 and 443. It uses a self-signed cert (`/etc/nginx/ssl/default.crt`)
|
|
and returns `444` (close connection) for any request whose SNI/Host does
|
|
not match an explicit vhost. ACME HTTP-01 challenges (`/.well-known/acme-challenge/`)
|
|
are still answered on port 80 so Certbot keeps working for new hostnames.
|
|
|
|
Without this, clients hitting the server IP directly (or doing HTTP/2
|
|
connection coalescing across vhosts sharing the same IP) would receive the
|
|
certificate of the first vhost loaded alphabetically, leaking that
|
|
hostname and breaking TLS verification on other vhosts.
|
|
|
|
Disable with `nginx_default_server_enabled: false`.
|
|
|
|
## Prometheus exporter
|
|
|
|
Optionally exposes nginx metrics (active connections, requests/sec) for
|
|
Prometheus via `nginx-prometheus-exporter`, which scrapes a loopback-only
|
|
`stub_status` endpoint this role deploys. Enable it:
|
|
|
|
```yaml
|
|
nginx_exporter_enabled: true
|
|
```
|
|
|
|
The exporter binds to `127.0.0.1:9113`; the `stub_status` block listens on
|
|
`127.0.0.1:8090` and denies non-localhost clients. Pair it with a scrape job:
|
|
|
|
```yaml
|
|
prometheus_scrape_configs:
|
|
- job_name: 'nginx'
|
|
static_configs:
|
|
- targets: ['127.0.0.1:9113']
|
|
```
|
|
|
|
## TLS certificates
|
|
|
|
Let's Encrypt (via the shared `certbot.yml` task) only works for publicly-resolvable hostnames. Each service role exposes a `<service>_nginx_letsencrypt` flag (default `false`) and two computed paths (`<service>_nginx_ssl_certificate` / `_key`): when the flag is off the vhost uses the shared self-signed cert (`nginx_default_ssl_cert`), when on it uses `/etc/letsencrypt/live/<hostname>/`. Gate the certbot include on the flag and reference the path variables from the vhost template. Set the flag `true` in inventory only once a public DNS record points at the host.
|
|
|
|
## Service Integration Pattern
|
|
|
|
Each service role should deploy its own vhost config:
|
|
|
|
**In service role tasks:**
|
|
|
|
```yaml
|
|
- name: Deploy nginx vhost
|
|
ansible.builtin.template:
|
|
src: nginx-vhost.conf.j2
|
|
dest: /etc/nginx/conf.d/myservice.conf
|
|
validate: nginx -t
|
|
when: myservice_nginx_enabled
|
|
notify: Reload nginx
|
|
|
|
- name: Remove nginx vhost when disabled
|
|
ansible.builtin.file:
|
|
path: /etc/nginx/conf.d/myservice.conf
|
|
state: absent
|
|
when: not myservice_nginx_enabled
|
|
notify: Reload nginx
|
|
```
|
|
|
|
## Transparent Proxy Forwarding
|
|
|
|
Forward TCP traffic from this Nginx instance to services on other hosts using the `stream` module (layer 4 proxy).
|
|
|
|
**Configuration:**
|
|
|
|
```yaml
|
|
nginx_forwarder:
|
|
"blog.hello.com":
|
|
forward_to: "my.host.lan"
|
|
http: true # Forward port 80 (default: true)
|
|
https: true # Forward port 443 (default: true)
|
|
```
|
|
|
|
**How it works:**
|
|
|
|
- **Stream-based TCP proxy** (layer 4, not HTTP layer 7)
|
|
- No protocol inspection - just forwards raw TCP packets
|
|
- **HTTPS passes through encrypted** - backend host handles TLS termination
|
|
- HTTP also uses stream (simpler, but no HTTP features like headers/logging)
|
|
|
|
**Use case:** Omega (gateway) forwards all traffic to Andromeda (internal server) that handles its own TLS certificates.
|
|
|
|
**Important notes:**
|
|
|
|
- Stream configs deployed to `/etc/nginx/streams.d/`
|
|
- No HTTP logging (stream doesn't understand HTTP protocol)
|
|
- No X-Forwarded-For headers (transparent TCP forwarding)
|
|
- Only ONE domain can use port 443 forwarding (TCP port limitation)
|
|
|
|
## Logging Backends
|
|
|
|
**journald (default):**
|
|
|
|
- Logs sent to systemd journal via syslog
|
|
- View: `journalctl -u nginx -f`
|
|
|
|
**file:**
|
|
|
|
- Traditional `/var/log/nginx/*.log` files
|
|
- Automatic logrotate configuration
|
|
|
|
Switch via `nginx_log_backend` variable.
|
|
|
|
## Hands-on Commands
|
|
|
|
```bash
|
|
# Test configuration
|
|
nginx -t
|
|
|
|
# Reload (zero downtime)
|
|
systemctl reload nginx
|
|
|
|
# View logs (journald)
|
|
journalctl -u nginx -f
|
|
journalctl -u nginx -n 100
|
|
journalctl -u nginx -p err
|
|
|
|
# View logs (file)
|
|
tail -f /var/log/nginx/access.log
|
|
tail -f /var/log/nginx/error.log
|
|
|
|
# List loaded vhosts
|
|
ls -la /etc/nginx/conf.d/
|
|
|
|
# List stream forwarders
|
|
ls -la /etc/nginx/streams.d/
|
|
```
|
|
|
|
## Configuration Variables
|
|
|
|
See [defaults/main.yml](defaults/main.yml) for all available variables.
|
|
|
|
## References
|
|
|
|
- [Nginx Documentation](https://nginx.org/en/docs/)
|
|
- [Nginx ACME Support](https://blog.nginx.org/blog/native-support-for-acme-protocol)
|
|
- [Nginx Stream Module](https://nginx.org/en/docs/stream/ngx_stream_core_module.html)
|
|
- [Nginx Logging](https://nginx.org/en/docs/syslog.html)
|
|
- [Nginx SSL/TLS](https://nginx.org/en/docs/http/configuring_https_servers.html)
|