nginx (Ansible role)
Installs and configures nginx for simple homelab / local-network setups, where most sites are "proxy a domain to a backend over HTTPS" and share a single wildcard cert.
This role exists because most of my nginx configs were nearly identical — same SSL params, same proxy headers, same redirect-to-HTTPS pattern. The role captures that shared shape, so each new service is a few lines of YAML instead of a copy-pasted server block.
Not for production. No Let's Encrypt automation, no per-vhost cert provisioning, no cleanup for sites removed from inventory, snakeoil cert as default. Override what you need.
Tested on Debian (bookworm/trixie) and Ubuntu (jammy/noble).
Quick start
- hosts: nginx
become: true
roles:
- role: nginx
vars:
# Override the default snakeoil cert with your real one
nginx_ssl_certificate: /etc/nginx/ssl/lab.local/fullchain.pem
nginx_ssl_certificate_key: /etc/nginx/ssl/lab.local/privkey.pem
nginx_sites:
- name: default # keep the catch-all
template_path: site-configs/default.conf.j2
- name: vaultwarden # your service
domain_names:
- vault.lab.local
upstream_url: http://192.168.1.10:8080
That's it. vault.lab.local now serves over HTTPS, redirects plain HTTP → HTTPS,
uses the shared cert, and proxies everything to 192.168.1.10:8080.
Per-site options (nginx_sites[*])
| Key | Type | Default | Notes |
|---|---|---|---|
name |
string | required | Filename in sites-available/. |
domain_names |
list | required * | Values for server_name. |
upstream_url |
string | required * | proxy_pass target (e.g. http://host:port). |
ssl |
bool | true |
Enable HTTPS listener + cert. |
allow_http |
bool | false |
Also serve content over plain HTTP. By default HTTP → 301 → HTTPS. |
websockets |
bool | false |
Include websockets.conf (upgrade headers). |
max_upload_size |
string | unset | client_max_body_size value, e.g. "50m". |
extra_parameters |
string | unset | Freeform nginx directives injected into the server block. |
https_port |
int | 443 |
Override HTTPS listen port. |
http_port |
int | 80 |
Override HTTP listen port. |
ssl_certificate |
path | nginx_ssl_certificate |
Per-site cert override. |
ssl_certificate_key |
path | nginx_ssl_certificate_key |
Per-site key override. |
enabled |
bool | nginx_site_enabled_by_default |
Create the sites-enabled/ symlink. Setting false removes it. |
template_path |
string | nginx_site_config_template |
Use a custom template (path relative to templates/). |
conf_file |
path | unset | Use a static file instead of the template (path relative to playbook). |
* Required when using the built-in template. If you supply conf_file or a custom
template_path, only name is required.
Role variables
| Variable | Default | Purpose |
|---|---|---|
nginx_ssl_certificate |
/etc/ssl/certs/ssl-cert-snakeoil.pem |
Default cert path for all sites. |
nginx_ssl_certificate_key |
/etc/ssl/private/ssl-cert-snakeoil.key |
Default key path. |
nginx_resolver |
127.0.0.53 valid=300s |
Global DNS resolver (used by OCSP stapling). Override for LAN DNS. |
nginx_resolver_timeout |
5s |
|
nginx_site_enabled_by_default |
true |
Whether sites without an explicit enabled get symlinked. |
nginx_delete_default_site_config |
false |
Delete Debian's sites-available/default (only matters if you remove the default entry from nginx_sites). |
nginx_site_config_template |
nginx-site.conf.j2 |
Default template used when a site has no template_path/conf_file. |
nginx_sites |
one entry (the catch-all default site) |
Your list of sites. |
nginx_snippets |
[proxy-headers, ssl-params, websockets, fastcgi-php] |
Static snippets dropped into /etc/nginx/snippets/. |
nginx_conf_d_templates |
[resolver.conf] |
Templated files rendered into /etc/nginx/conf.d/. |
Path variables (nginx_site_config_path, nginx_site_enabled_path,
nginx_snippets_path, nginx_conf_d_path) exist for completeness and default to
Debian's canonical locations. You usually shouldn't touch them.
The default catch-all site
The shipped default entry (name: default) renders
templates/site-configs/default.conf.j2, which:
- Listens on
:80withdefault_server, 301-redirects any unknownHost:to HTTPS. - Listens on
:443 ssl http2withdefault_server, returns404.
It exists so unknown-host traffic doesn't leak one of your real sites' certs/content
via SNI scans. Keep it if you have more than one site; remove it (and set
nginx_delete_default_site_config: true) if you have exactly one.
Custom static configs
For sites that don't fit the template — e.g. a static file server, a non-proxy site, an upstream block — write the full nginx config yourself and point at it:
- name: media
conf_file: site-configs/media.conf # path relative to your playbook
The role copies it verbatim into sites-available/.
Caveats
- Snakeoil default cert. The role works out of the box but presents an untrusted
self-signed cert. Set
nginx_ssl_certificate(_key)to your real cert before serving anything real. - One shared cert by default. A single wildcard is the assumed homelab pattern.
Per-site override via
ssl_certificate/ssl_certificate_keyworks for the exceptions. - No removal cleanup. If you delete a site from
nginx_sites, the existingsites-available/<name>andsites-enabled/<name>are not removed. Clean up manually or setenabled: falseand let the symlink task remove the link. - Debian/Ubuntu only. Paths assume the Debian-style
sites-available/+sites-enabled/split. Won't work on Alpine, RHEL, etc. without surgery. - No Let's Encrypt / ACME. Bring your own certs.
- Resolver default assumes systemd-resolved. Override
nginx_resolverif you use a different local resolver (e.g."dns.home valid=300s").