.. index:: ! nginx
Nginx
=====
.. contents::
`Nginx docs are here `_, but good luck finding anything there
if you don't already where it is.
I *HIGHLY RECOMMEND* going to
`https://ssl-config.mozilla.org `_
to generate a base config file, then customizing it.
.. contents::
Let's Encrypt
-------------
Let's Encrypt's `certbot `_ utility has an ``nginx``
option that doesn't do what I initially thought it did. It does not try to
automatically figure out from your nginx config what you're trying to do and update
your config correctly to use a Let's Encrypt certificate.
Instead, the ``nginx`` option temporarily changes your nginx configuration
to serve the response to the Let's Encrypt http challenge, does the certificate
request, restores your configuration, then tells you where it put the resulting
certificate and key (if successful)::
root@junebug:~# certbot certonly -m dan@poirier.us --test-cert --agree-tos --cert-name=testit --non-interactive --nginx -d www.example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/testit/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/testit/privkey.pem
Your cert will expire on 2021-09-20. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
The nice things about this is that
you don't have to configure nginx yourself in a way that will let it both
start without having a certificate yet and serve the certbot challenge.
.. warning:: If something goes wrong, certbot can leave an instance of nginx running that is not managed by your service manager (e.g. systemd) and mess things up until you kill it manually.
Commented config file
---------------------
FIND OUT: what happens if we get a port 443 ssl request for a hostname
that we're not serving? Does nginx reject it completely, or try to serve
it with some existing hostname configuration?
This started from https://ssl-config.mozilla.org but is heavily modified.
.. code-block::
# Put this at
# /etc/nginx/conf.d/00-default-vhost.conf
# It returns a 410 for any port 80 request for a domain name we're not serving with
# a more specific configuration.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 410;
log_not_found off;
server_tokens off;
}
Now for each site ``mysite.example.com`` that you want to serve...
.. index:: nginx; ssl redirection
.. code-block::
# /etc/nginx/sites-enabled/mysite.example.com.conf
server {
listen 80; # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
listen [::]:80;
server_name example.com www.example.com; # http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name
location '/.well-known/acme-challenge' {
# Don't redirect Let's Encrypt to https
root /var/www/mysite.example.com;
}
location / {
# Redirect to https
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2; # http://nginx.org/en/docs/http/ngx_http_core_module.html#listen
listen [::]:443 ssl http2;
server_name example.com www.example.com; # http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name
root /var/www/mysite.example.com;
# modern SSL configuration
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_certificate /path/to/signed_cert_plus_intermediates;
ssl_certificate_key /path/to/private_key;
ssl_session_timeout 1d;
ssl_session_cache shared:MySiteExampleCom:10m; # about 40000 sessions
ssl_session_tickets off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
# Do NOT uncomment this until you're SURE your https is working and will
# continue working. You might set max-age very short for testing until
# then. Do an internet search for more about HSTS.
# add_header Strict-Transport-Security "max-age=63072000" always;
}
Most useful variables
---------------------
.. index:: nginx; variables
$host
in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request
$http_host
Value of the "Host:" header in the request (same as all $http_ variables)
$https
“on” if connection operates in SSL mode, or an empty string otherwise
$request_method
request method, usually “GET” or “POST”
$request_uri
full original request URI (with arguments)
$scheme
request scheme, e.g. “http” or “https”
$server_name
name of the server which accepted a request
$server_port
port of the server which accepted a request
Variables in configuration files
--------------------------------
.. index:: nginx; using variables
See above for "variables" that get set automatically for each request
(and that we cannot modify).
The ability to set variables at runtime and control logic flow based on them
is part of the `rewrite module `_
and *not* a general feature of nginx.
You can `set `_ a
variable::
Syntax: set $variable value;
Default: —
Context: server, location, if
"The value can contain text, variables, and their combination." -- but I have not yet found
the documentation on how these can be "combined".
Then use `if `_ etc.::
Syntax: if (condition) { rewrite directives... }
Default: —
Context: server, location
Conditions can include::
* a variable name; false if the value of a variable is an empty string or “0”;
* comparison of a variable with a string using the “=” and “!=” operators;
* matching of a variable against a regular expression using the “~” (for case-sensitive matching) and “~*” (for case-insensitive matching) operators. Regular expressions can contain captures that are made available for later reuse in the $1..$9 variables. Negative operators “!~” and “!~*” are also available. If a regular expression includes the “}” or “;” characters, the whole expressions should be enclosed in single or double quotes.
* checking of a file existence with the “-f” and “!-f” operators;
* checking of a directory existence with the “-d” and “!-d” operators;
* checking of a file, directory, or symbolic link existence with the “-e” and “!-e” operators;
* checking for an executable file with the “-x” and “!-x” operators.
Examples::
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
}
if ($request_method = POST) {
return 405;
}
if ($slow) {
limit_rate 10k;
}
if ($invalid_referer) {
return 403;
}
.. warning::
You *CANNOT* put any directive you want inside the ``if``,
only rewrite directives like ``set``, ``rewrite``, ``return``, etc.
.. warning::
The values of variables you set this way can *ONLY* be used in ``if`` conditions,
and maybe rewrite directives; don't try to use them elsewhere.
Let's Encrypt
-------------
Based rather loosely on `https://certbot.eff.org/lets-encrypt/pip-nginx `_.
* Before you start, your site must already be on the internet accessible using all the domain names you
want certificates for, at port 80, and without
any automatic redirect to port 443. If that makes you paranoid, you can configure nginx to redirect
80 to 443 except for /.well-known/acme-challenge. Here's an unsupported example::
server {
listen 80;
location '/.well-known/acme-challenge' {
root /var/www/demo;
}
location / {
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
* Install certbot. Assuming Ubuntu, "sudo apt install certbot python3-certbot-nginx" should do it.
* Run "sudo certbot certonly --nginx" and follow the instructions.
* Set up automatic renewal. This will add a cron command to do it::
echo "0 0,12 * * * root /usr/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
* run "sudo certbot renew --dry-run" to test renewal