Compare commits

...

2 Commits

Author SHA1 Message Date
9790415da1 roles/nginx: use nginx-acme-module for TLS certs
Use the nginx-acme module for TLS certs instead of acme.sh because
it requires less configuration, less tooling, and works using the
tls-alpn challenge.
2026-01-06 12:15:46 +03:00
85473f0fe9 README.md: add restic to TODO 2026-01-06 09:39:39 +03:00
11 changed files with 50 additions and 124 deletions

View File

@@ -20,6 +20,10 @@ Once you've satisfied the the above assumptions, you can execute:
$ ansible-playbook web.yml $ ansible-playbook web.yml
## TODO
- Switch from tarsnap to restic
## License ## License
Copyright (C) 20142021 Alan Orth Copyright (C) 20142021 Alan Orth

View File

@@ -16,26 +16,19 @@ nginx_ssl_dhparam: /etc/ssl/certs/dhparam.pem
nginx_ssl_protocols: TLSv1.2 TLSv1.3 nginx_ssl_protocols: TLSv1.2 TLSv1.3
nginx_ssl_ecdh_curve: X25519:prime256v1:secp384r1 nginx_ssl_ecdh_curve: X25519:prime256v1:secp384r1
# DNS resolvers for OCSP stapling (default to Cloudflare public DNS) # DNS resolvers (default to Cloudflare public DNS)
# See: https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_stapling nginx_resolver: 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]
nginx_ssl_stapling_resolver: 1.1.1.1 1.0.0.1 [2606:4700:4700::1111] [2606:4700:4700::1001]
# HTTP Strict-Transport-Security header, recommended by Google to be ~1 year # HTTP Strict-Transport-Security header, recommended by Google to be ~1 year
# in seconds, see: https://hstspreload.org/ # in seconds, see: https://hstspreload.org/
nginx_hsts_max_age: 31536000 nginx_hsts_max_age: 31536000
# install acme.sh? # Use Let's Encrypt via nginx's acme module?
# true unless you're in development and using "localhost" + snakeoil certs # true unless you're in development and using "localhost" + snakeoil certs
use_letsencrypt: true use_letsencrypt: true
# Directory root for Let's Encrypt certs # Email address to use for the ACME account managing the site's certificates.
letsencrypt_root: /etc/ssl letsencrypt_contact: foo@example.com
# Location where to save initial acme.sh script. After installation the script
# will automatically create its home in the /root/.acme.sh directory (including
# a copy of the script itself). The initial script is not needed after.
letsencrypt_acme_script_temp: /root/acme.sh
letsencrypt_acme_home: /root/.acme.sh
# stable is 1.26.x # stable is 1.26.x
# mainline is 1.27.x # mainline is 1.27.x

View File

@@ -1,12 +0,0 @@
[Unit]
Description=Renew Let's Encrypt certificates
[Timer]
# twice a day, at midnight and noon
OnCalendar=*-*-* 00,12:00:00
# Add a random delay of 03600 seconds
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target

View File

@@ -1,7 +1,6 @@
--- ---
# Use acme.sh instead of certbot because they only support installation via # Remove any standalone Let's Encrypt clients because we use nginx's acme module.
# snap now. - name: Remove standalone Let's Encrypt clients
- name: Install and configure Let's Encrypt
tags: letsencrypt tags: letsencrypt
when: when:
- ansible_facts["distribution"] == 'Debian' - ansible_facts["distribution"] == 'Debian'
@@ -20,72 +19,17 @@
- /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh - /etc/letsencrypt/renewal-hooks/pre/stop-nginx.sh
- /etc/letsencrypt/renewal-hooks/post/start-nginx.sh - /etc/letsencrypt/renewal-hooks/post/start-nginx.sh
- name: Check if acme.sh is installed - name: Remove unused Let's Encrypt well-known directory
ansible.builtin.stat:
path: "{{ letsencrypt_acme_home }}"
register: acme_home
- name: Download acme.sh
when: not acme_home.stat.exists
ansible.builtin.get_url:
url: https://raw.githubusercontent.com/acmesh-official/acme.sh/master/acme.sh
dest: "{{ letsencrypt_acme_script_temp }}"
mode: "0700"
register: acme_download
# Run the "install" for acme.sh so it creates the .acme.sh dir (currently I
# have to chdir to the /root directory where the script exists or else it
# fails. Ansible runs it, but the script can't find itself...).
- name: Install acme.sh
when: acme_download is changed
ansible.builtin.command:
cmd: "{{ letsencrypt_acme_script_temp }} --install --no-profile --no-cron"
creates: "{{ letsencrypt_acme_home }}/acme.sh"
chdir: /root
register: acme_install
- name: Remove temporary acme.sh script
when:
- acme_install.rc is defined
- acme_install.rc == 0
ansible.builtin.file: ansible.builtin.file:
dest: "{{ letsencrypt_acme_script_temp }}"
state: absent state: absent
- name: Set default certificate authority for acme.sh
ansible.builtin.command:
cmd: "{{ letsencrypt_acme_home }}/acme.sh --set-default-ca --server letsencrypt"
- name: Prepare Let's Encrypt well-known directory
ansible.builtin.file:
state: directory
path: /var/lib/letsencrypt/.well-known path: /var/lib/letsencrypt/.well-known
owner: root
group: nginx
mode: g+s
- name: Copy systemd service to renew Let's Encrypt certs - name: Remove unused Let's Encrypt systemd units
ansible.builtin.template: ansible.builtin.file:
src: renew-letsencrypt.service.j2 state: absent
dest: /etc/systemd/system/renew-letsencrypt.service path: "{{ item }}"
mode: "0644" loop:
owner: root - /etc/systemd/system/renew-letsencrypt.service
group: root - /etc/systemd/system/renew-letsencrypt.timer
- name: Copy systemd timer to renew Let's Encrypt certs
ansible.builtin.copy:
src: renew-letsencrypt.timer
dest: /etc/systemd/system/renew-letsencrypt.timer
mode: "0644"
owner: root
group: root
# always issues daemon-reload just in case the service/timer changed
- name: Start and enable systemd timer to renew Let's Encrypt certs
ansible.builtin.systemd_service:
name: renew-letsencrypt.timer
state: started
enabled: true
daemon_reload: true
# vim: set ts=2 sw=2: # vim: set ts=2 sw=2:

View File

@@ -31,25 +31,28 @@
- name: Install nginx - name: Install nginx
ansible.builtin.apt: ansible.builtin.apt:
pkg: nginx pkg: [nginx, nginx-module-acme]
cache_valid_time: 3600 cache_valid_time: 3600
state: present state: present
tags: tags:
- nginx - nginx
- packages - packages
- name: Copy nginx.conf - name: Copy nginx configs (templates)
ansible.builtin.template: ansible.builtin.template:
src: nginx.conf.j2 src: "{{ item.src }}"
dest: /etc/nginx/nginx.conf dest: "{{ item.dest }}"
mode: "0644" mode: "0644"
owner: root owner: root
group: root group: root
loop:
- { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf }
- { src: 01-acme.conf.j2, dest: /etc/nginx/conf.d/01-acme.conf }
notify: notify:
- Reload nginx - Reload nginx
tags: nginx tags: nginx
- name: Copy extra nginx configs - name: Copy nginx configs (files)
ansible.builtin.copy: ansible.builtin.copy:
src: "{{ item }}" src: "{{ item }}"
dest: /etc/nginx/{{ item }} dest: /etc/nginx/{{ item }}

View File

@@ -0,0 +1,14 @@
{{ ansible_managed | comment }}
# Setting the resolver is important because nginx uses it for upstream servers
# and ACME requests. nginx attempts to resolve these at startup and will fail
# if it cannot.
resolver {{ nginx_resolver }};
acme_issuer letsencrypt {
uri https://acme-v02.api.letsencrypt.org/directory;
contact {{ letsencrypt_contact }};
challenge tls-alpn;
state_path /var/cache/nginx/acme-letsencrypt;
accept_terms_of_service;
}

View File

@@ -14,10 +14,13 @@
{# otherwise, assume host is using letsencrypt #} {# otherwise, assume host is using letsencrypt #}
{% else %} {% else %}
# concatenated key + cert acme_certificate letsencrypt;
# See: http://nginx.org/en/docs/http/configuring_https_servers.html
ssl_certificate {{ letsencrypt_root }}/certs/{{ domain_name }}.fullchain.pem; ssl_certificate $acme_certificate;
ssl_certificate_key {{ letsencrypt_root }}/private/{{ domain_name }}.key.pem; ssl_certificate_key $acme_certificate_key;
# do not parse the certificate on each request
ssl_certificate_cache max=2;
{% endif %} {% endif %}
@@ -31,14 +34,6 @@
ssl_ciphers "{{ tls_cipher_suite }}"; ssl_ciphers "{{ tls_cipher_suite }}";
ssl_prefer_server_ciphers off; ssl_prefer_server_ciphers off;
{# OSCP stapling only works with real certs #}
{% if use_letsencrypt == true or item.tls_certificate_path %}
# OCSP stapling...
ssl_stapling on;
ssl_stapling_verify on;
resolver {{ nginx_ssl_stapling_resolver }};
{% endif %} {# end: use_letsencrypt #}
{% if enable_hsts == true %} {% if enable_hsts == true %}
# Enable this if you want HSTS (recommended, but be careful) # Enable this if you want HSTS (recommended, but be careful)
# Include all subdomains and indicate to Google that we want this pre-loaded in Chrome's HSTS store # Include all subdomains and indicate to Google that we want this pre-loaded in Chrome's HSTS store

View File

@@ -14,6 +14,8 @@ error_log /var/log/nginx/error.log warn;
# The file storing the process ID of the main process # The file storing the process ID of the main process
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
load_module modules/ngx_http_acme_module.so;
events { events {
# If you need more connections than this, you start optimizing your OS. # If you need more connections than this, you start optimizing your OS.
# That's probably the point at which you hire people who are smarter than you as this is *a lot* of requests. # That's probably the point at which you hire people who are smarter than you as this is *a lot* of requests.

View File

@@ -1,9 +0,0 @@
[Unit]
Description=Renew Let's Encrypt certificates
ConditionFileIsExecutable={{ letsencrypt_acme_home }}/acme.sh
[Service]
Type=oneshot
ExecStart={{ letsencrypt_acme_home }}/acme.sh --cron --home {{ letsencrypt_acme_home }} --reloadcmd "/bin/systemctl reload nginx" -w /var/lib/letsencrypt
SuccessExitStatus=0 2

View File

@@ -21,8 +21,6 @@ server {
listen [::]:80; listen [::]:80;
server_name {{ domain_name }} {{ domain_aliases }}; server_name {{ domain_name }} {{ domain_aliases }};
{% include 'well-known.j2' %}
# redirect http -> https # redirect http -> https
location / { location / {
# ? in rewrite makes sure nginx doesn't append query string again # ? in rewrite makes sure nginx doesn't append query string again

View File

@@ -1,6 +0,0 @@
location ^~ /.well-known/acme-challenge/ {
allow all;
root /var/lib/letsencrypt/;
default_type "text/plain";
try_files $uri =404;
}