Let's encrypt it all
Using letsencrypt
certbot
nginx preparation
Snippets
we require a directory (.well-known/acme-challenge/), that is writable by certbot (root) to place a challenge response on each domain
the directory must be servable (readable) by nginx (usually running with the user and group http)
-
/etc/nginx/letsencrypt-challenge.conf
-
/etc/nginx/nginx.conf
worker_processes auto; error_log /var/log/nginx/error.log; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; gzip on; sendfile on; keepalive_requests 55; keepalive_timeout 55; # pelican blog include domain.conf; # ownCloud include cloud.domain.conf; # roundcube mail interface available only through VPN include mail.domain.conf; }
-
/etc/nginx/geoblock.conf
-
/etc/nginx/letsencrypt-request-check.conf
-
/etc/nginx/domain.conf & /etc/nginx/cloud.domain.conf
-
/etc/nginx/mail.domain.conf
certbot staging
Production
moar snippets
-
/etc/nginx/tls.conf
ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets off; ssl_dhparam /etc/nginx/dhparam.pem; ssl_protocols TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem; resolver 8.8.8.8;
-
/etc/nginx/security_headers.conf
domain configurations
-
/etc/nginx/domain.conf:
# redirect all unencrypted traffic to https server { listen 80 default_server; server_name domain.tld www.domain.tld; return 301 https://domain.tld$request_uri; } # redirect all traffic to www. to the plain url server { listen 443 ssl; listen [::]:443 ssl; server_name www.domain.tld; return 301 https://domain.tld$request_uri; } server { listen 443 default_server; listen [::]:443 ssl default_server; server_name domain.tld; include tls.conf; # your pelican blog resides here root /srv/http/websites/domain.tld; # make sure to log access_log /var/log/nginx/access.domain.log; error_log /var/log/nginx/error.domain.log; error_page 403 404 /404/index.html; error_page 500 502 503 504 /50x.html; # include security headers include security_headers.conf; add_header Content-Security-Policy "default-src 'self'; connect-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'"; # include the letsencrypt snippet include letsencrypt-challenge.conf; location / { index index.html index.htm; try_files $uri $uri/ $uri/index.html; } location = /robots.txt { allow all; log_not_found off; access_log off; } location = /50x.html { root /usr/share/nginx/html; } }
-
/etc/nginx/cloud.domain.conf
# redirect all unencrypted traffic to https server { listen 80; listen [::]:80; server_name cloud.domain.tld www.cloud.domain.tld; return 301 https://cloud.domain.tld$request_uri; } # redirect www. to the plain domain server { listen 443 ssl; listen [::]:443 ssl; server_name www.cloud.domain.tld; return 301 https://cloud.domain.tld$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name cloud.domain.tld; include tls.conf; error_page 403 /core/templates/403.php; error_page 404 /core/templates/404.php; # make sure to log access_log /var/log/nginx/access.cloud.domain.log; error_log /var/log/nginx/error.cloud.domain.log; #this is to avoid Request Entity Too Large error client_max_body_size 10G; # include security headers (the rest are set by ownCloud itself already) add_header Content-Security-Policy "default-src 'self'; connect-src 'self'; img-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline' 'unsafe-eval'"; add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"; # include the letsencrypt snippet include letsencrypt-challenge.conf; location = /robots.txt { allow all; log_not_found off; access_log off; } location ~ ^/(?:\.htaccess|data|config|db_structure\.xml|README) { deny all; log_not_found off; access_log off; } location ~ ^(.+\.php)(.*)$ { include uwsgi_params; uwsgi_modifier1 14; uwsgi_pass unix:/run/uwsgi/owncloud.sock; uwsgi_intercept_errors on; } location / { root /usr/share/webapps/owncloud; index index.php; rewrite ^/.well-known/host-meta /public.php?service=host-meta last; rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; rewrite ^/.well-known/carddav /remote.php/dav/ redirect; rewrite ^/.well-known/caldav /remote.php/dav/ redirect; rewrite ^(/core/doc/[^\/]+/)$ $1/index.html; rewrite ^/caldav(.*)$ /remote.php/dav$1 redirect; rewrite ^/carddav(.*)$ /remote.php/dav$1 redirect; rewrite ^/webdav(.*)$ /remote.php/dav$1 redirect; try_files $uri $uri/ /index.php; } location ~ ^/.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ { expires 30d; access_log off; } }
-
/etc/nginx/mail.domain.conf
# include the geoblock snippet include geoblock.conf; # redirect all unencrypted traffic to https server { listen 80; listen [::]:80; server_name mail.domain.tld www.mail.domain.tld; return 301 https://mail.domain.tld$request_uri; } # redirect www. to the plain domain server { listen 443; listen [::]:443 ssl; server_name www.mail.domain.tld; return 301 https://mail.domain.tld$request_uri; } server { listen 443 ssl; listen [::]:443 ssl; server_name mail.domain.tld; include tls.conf; # make sure to log access_log /var/log/nginx/access.mail.domain.log; error_log /var/log/nginx/error.mail.domain.log; root /usr/share/webapps/roundcubemail; #this is to avoid Request Entity Too Large error client_max_body_size 20M; # include security headers include security_headers.conf; add_header Content-Security-Policy "default-src 'self'; connect-src 'self'; img-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"; # include the request-check snippet include letsencrypt-challenge.conf; # include the letsencrypt snippet include letsencrypt-challenge.conf; location / { index index.php; try_files $uri $uri/$args @roundcubemail; } location @roundcubemail { include uwsgi_params; uwsgi_modifier1 14; uwsgi_pass unix:/run/uwsgi/roundcubemail.sock; } location ~ ^/favicon.ico$ { root /usr/share/webapps/roundcubemail/skins/classic/images; log_not_found off; access_log off; expires max; } location = /robots.txt { allow all; log_not_found off; access_log off; expires 30d; } # Deny serving some files location ~ ^/(composer\.json-dist|composer\.json|package\.xml|CHANGELOG|INSTALL|LICENSE|README\.md|UPGRADING|bin|config|installer|program\/(include|lib|localization|steps)|SQL|tests)$ { deny all; } # Deny serving files beginning with a dot, but allow letsencrypt acme-challenge location ~ /\.(?!well-known/acme-challenge) { deny all; access_log off; log_not_found off; } }
Bringing it up
You should now check your nginx configuration (as root):
Postfix
Your mail server can also use this certificate now (if your MX record points to one of the domains the certificate was issued for).
-
/etc/postfix/main.cf
Dovecot
The same counts for your IMAP server:
-
/etc/dovecot/dovecot.conf
Prosody
-
/etc/prosody/prosody.cfg.lua
Renewal
-
/etc/systemd/system/certbot.service
[Unit] Description=Let's Encrypt renewal [Service] Type=oneshot ExecStart=/usr/bin/certbot renew --quiet --agree-tos ExecStartPost=/usr/bin/cp --remove-destination /etc/letsencrypt/live/domain.tld/fullchain.pem /etc/letsencrypt/live/domain.tld/privkey.pem /etc/prosody/certs/ ExecStartPost=/usr/bin/chown prosody /etc/prosody/certs/fullchain.pem ; /usr/bin/chown prosody /etc/prosody/certs/privkey.pem ; /usr/bin/chmod u-w,g-r,o-r /etc/prosody/certs/privkey.pem StandardError=syslog NotifyAccess=all KillSignal=SIGQUIT PrivateDevices=yes PrivateTmp=yes ProtectSystem=full ReadWriteDirectories=/etc/letsencrypt /etc/prosody/certs ProtectHome=yes NoNewPrivileges=yes
-
/etc/systemd/system/certbot.timer