Files
ansible-playbooks/roles/nginx/tasks/main.yml
T
Clément Désiles 314fa715fd fix(nginx): prevent cert leak on IPv6 / unknown SNI
Two issues caused TLS to break on photos.carabosse.cloud over IPv6
(GrapheneOS + Immich app via Orange 5G NAT64):

1. Per-service vhosts only listened on IPv4 (listen 443 ssl). On IPv6,
   nginx fell back to the first vhost loaded alphabetically and served
   its certificate, breaking hostname verification on every other vhost.

2. /etc/letsencrypt/{live,archive} were 0700 root:root after certbot
   created them, so the nginx worker (user http on Arch) could not read
   the chained intermediates and served the leaf-only chain.

Changes:
- Add catch-all 00-default.conf default_server on :80 and :443 (v4+v6)
  with a self-signed cert and 'return 444'. ACME challenges still
  answered on :80.
- Add IPv6 listeners ([::]:80 and [::]:443 ssl) to immich, gitea, ntfy,
  uptime_kuma vhosts and to the temporary ACME provisioning vhost.
- Apply 0755 on /etc/letsencrypt/live and /etc/letsencrypt/archive on
  every run, not only at initial cert provisioning.
2026-05-30 17:06:10 +02:00

210 lines
5.4 KiB
YAML

---
- name: Load OS-specific variables
ansible.builtin.include_vars: "{{ item }}"
with_first_found:
- "{{ ansible_facts['os_family'] }}.yml"
- debian.yml
- name: Set nginx_user if not already set
ansible.builtin.set_fact:
nginx_user: "{{ nginx_user | default('www-data') }}"
- name: Add Nginx official APT signing key (Debian/Ubuntu)
ansible.builtin.get_url:
url: https://nginx.org/keys/nginx_signing.key
dest: /etc/apt/keyrings/nginx-archive-keyring.asc
mode: "0644"
when:
- ansible_facts['os_family'] == 'Debian'
- name: Add Nginx official repository (Debian/Ubuntu)
ansible.builtin.deb822_repository:
name: nginx-official
types: deb
uris: http://nginx.org/packages/mainline/debian/
suites: "{{ ansible_facts['distribution_release'] }}"
components: nginx
signed_by: /etc/apt/keyrings/nginx-archive-keyring.asc
state: present
when:
- ansible_facts['os_family'] == 'Debian'
- name: Install nginx
ansible.builtin.package:
name: nginx
state: present
- name: Install nginx stream module (Debian)
ansible.builtin.package:
name: libnginx-mod-stream
state: present
when:
- ansible_facts['os_family'] == 'Debian'
- nginx_forwarder is defined
- nginx_forwarder | length > 0
- name: Install Certbot
ansible.builtin.package:
name: certbot
state: present
when: acme_email is defined
- name: Enable Certbot renewal timer
ansible.builtin.systemd:
name: "{{ certbot_timer }}"
enabled: true
state: started
when: acme_email is defined
- name: Remove default nginx vhost (Arch ships one that conflicts)
ansible.builtin.file:
path: "{{ nginx_conf_dir }}/default.conf"
state: absent
notify: Reload nginx
- name: Ensure nginx conf.d directory exists
ansible.builtin.file:
path: "{{ nginx_conf_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Ensure nginx streams.d directory exists
ansible.builtin.file:
path: "{{ nginx_streams_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure catch-all default_server
when: nginx_default_server_enabled
block:
- name: Ensure nginx ssl directory exists
ansible.builtin.file:
path: "{{ nginx_default_ssl_cert | dirname }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Generate self-signed cert for default_server
ansible.builtin.command:
cmd: >-
openssl req -x509 -nodes -newkey rsa:2048
-keyout {{ nginx_default_ssl_key }}
-out {{ nginx_default_ssl_cert }}
-days 3650 -subj "/CN=default"
creates: "{{ nginx_default_ssl_cert }}"
- name: Restrict permissions on default_server key
ansible.builtin.file:
path: "{{ nginx_default_ssl_key }}"
owner: root
group: root
mode: "0600"
- name: Deploy default_server vhost
ansible.builtin.template:
src: default-server.conf.j2
dest: "{{ nginx_conf_dir }}/00-default.conf"
owner: root
group: root
mode: "0644"
notify: Reload nginx
- name: Remove default_server vhost when disabled
ansible.builtin.file:
path: "{{ nginx_conf_dir }}/00-default.conf"
state: absent
when: not nginx_default_server_enabled
notify: Reload nginx
- name: Ensure Certbot webroot directory exists
ansible.builtin.file:
path: /var/www/certbot
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: "0755"
when: acme_email is defined
- name: Deploy nginx main configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
validate: nginx -t -c %s
notify: Reload nginx
- name: Deploy stream forwarder configurations
ansible.builtin.template:
src: forwarder.conf.j2
dest: "{{ nginx_streams_dir }}/forwarder-{{ domain | replace('.', '_') }}.conf"
owner: root
group: root
mode: "0644"
loop: "{{ nginx_forwarder | dict2items }}"
loop_control:
loop_var: item
vars:
domain: "{{ item.key }}"
config: "{{ item.value }}"
when:
- nginx_forwarder is defined
- nginx_forwarder | length > 0
notify: Reload nginx
- name: Validate nginx configuration after stream forwarder deployment
ansible.builtin.command: nginx -t
changed_when: false
when:
- nginx_forwarder is defined
- nginx_forwarder | length > 0
- name: Deploy logrotate configuration for nginx
ansible.builtin.template:
src: logrotate-nginx.j2
dest: /etc/logrotate.d/nginx
owner: root
group: root
mode: "0644"
when: nginx_log_backend == 'file'
- name: Remove logrotate configuration when using journald
ansible.builtin.file:
path: /etc/logrotate.d/nginx
state: absent
when: nginx_log_backend == 'journald'
- name: Allow HTTP traffic through firewall
community.general.ufw:
rule: allow
port: "80"
proto: tcp
comment: Nginx HTTP
retries: 5
delay: 2
register: ufw_result
until: ufw_result is succeeded
- name: Allow HTTPS traffic through firewall
community.general.ufw:
rule: allow
port: "443"
proto: tcp
comment: Nginx HTTPS
retries: 5
delay: 2
register: ufw_result
until: ufw_result is succeeded
- name: Enable and start nginx service
ansible.builtin.systemd:
name: nginx
enabled: true
state: started