[ACTUALIZADO 2] De HTTP a HTTPS con Nginx y Let’s Encrypt corriendo aplicaciones Node

Introducción

Hoy en día y sabiendo los problemas de seguridad que existen al navegar por redes sin cables públicas, es vital que nuestra web emplee certificados ssl. Si bien, por ejemplo este blog tampoco creo que lo necesite debo configurarlo igualmente ya que, entre otros motivos, Google penaliza los sitios web que no usan https.

Hace unos cuantos años no era habitual tener una web con https, más bien se usaba solo en comercio electrónico para garantizar la seguridad. Hoy en día debido a la popularización de herramientas de seguridad que permiten a un atacante interceptar todo el tráfico wifi y, al no disponer este de cifrado alguno, todo tú trafico web queda expuesto es por ello que Google para incentivar que se cifre todo ese contenido “obliga” a las webs penalizándolas si no tienen https habilitado.

Precio

Existen una gama de certificados baratos que son más que suficiente en muchos casos. Normalmente esto no se debe solo a las características técnicas que nos aporta cada certificado en función de su valor sino también del seguro adicional que nos venden (ejemplo: transacciones de hasta 10.000 €) y las comprobaciones que hacen para verificar el certificado.

Aunque de hace un tiempo a esta parte se ha llevado un proyecto a cabo por parte de Mozilla, Cisco, Google, OVH y muchos más, llamado Let’s Encrypt (en adelante, letsencrypt) que proporciona certificados válidos (para los navegadores) y con un nivel de seguridad suficiente en muchos casos. Estos certificados son totalmente gratuitos y se proporcionan en intervalos de 3 meses renovables.

¿Qué vamos a aprender?

  • Nociones básicas sobre qué es lo que vamos a hacer.
  • Instalar nginx y sus dependencias para poner en marcha el servidor web con un certificado SSL RSA válido.
  • Configurar correctamente la validación ACME.
  • Configurar el servidor http/s para servir una web estática.
  • Configurar el servidor http/s para servir una aplicación node configurando nginx como proxy.
  • Generar los certificados y poner todo en marcha.
  • Comprobar los certificados.
  • Hacer que nuestros certificados se renueven automáticamente.

 Advertencias

  • Cuando se pone un comando de consola (bash) hay que tener en cuenta que si va precedido de # (almohadilla) se ejecuta ese comando como un usuario normal, y si va precedido de $ (símbolo de dólar) se ejecuta como usuario privilegiado (root).
  • No se necesita una ip dedicada.
  • La configuración aquí expuesta es real y se usa en producción, si encuentras algún fallo avisame por favor.
  • Aunque la configuración es real ciertos parámetros pueden estar modificados por motivos de seguridad (contraseñas, usuarios…).
  • Todo en este artículo está pensado para obtener certificados ssl corriendo un servidor web (Nginx en este caso).
  • Yo uso vim para modificar archivos de texto, puedes usar cualquier otro que te resulte más cómodo, como nano por poner un ejemplo.
  • Normalmente pondré el código o configuración y luego la explicación así que no te preocupes si no entiendes algo, sigue leyendo.
  • Cuando creo scripts, configuraciones, etc. Uso comentarios para documentarlo todo en inglés, así que perdona si me he dejado algún comentario sin traducir. También he borrado algunos comentarios que no consideraba útiles para el artículo.

Requisitos

  • Servidor Linux, aquí usamos un servidor que corre ubuntu server.
  • Tener conocimientos de la shell de tú linux, especialmente si no es exactamente igual y debes adaptar comandos (por ejemplo en centos no se usa apt para instalar paquetes).
  • Tener el dominio/s apuntando al servidor en el que quieres correr el servidor web.
  • Tener el firewall con los puertos 80 y 443 abiertos.

Nociones sobre https: ¿Qué es…?

HTTPS son las siglas de Hypertext Transfer Protocol Secure y pretende aportar una capa de seguridad encriptando el tráfico entre el cliente y el servidor web. Si quieres más información puedes ir a la wikipedia.

Nota: En este caso y debido a vulnerabilidades detectadas en ssl2 y ssl3 vamos a usar TLS únicamente.

ACME

ACME8 9 es un estándar creado para definir como autentificar que un dominio es quien dice ser y poder verificar los certificados ssl, sin esa verificación tus certificados no estarán en vigor y por lo tanto no validarán.

Para ello Let’s Encrypt hará una petición a nuestro dominio tal que domain.tdl/.well-known/acme-challenge/[random string] y comprobará que [random string] existe, para verificar el dominio.

Nota: Las peticiones ACME son por el puerto 80.

OCSP

OCSP es una forma alternativa a las listas de revocación (CRL; Certificate Revocation List) que pretende hacer más sencilla la verificación de nuestros certificados. Si quieres más información, como siempre, en la wikipedia.

Diffie-Hellman

Es un protocolo para establecer claves entre dos partes que no han mantenido contacto previo y por un canal de comunicaciones inseguro. Una vez más, wikipedia.

Let’s Encrypt

Let’s Encrypt es una autoridad certificadora llevada a cabo por diversas organizaciones importantes para proporcionar certificados ssl gratuitos, más información en la web oficial. Es una iniciativa del grupo californiano Internet Security Research Group.

Límites de Let’s Encrypt

Obviamente a todos nos encanta lo gratis, por ello han puesto varias limitaciones que no deben preocuparnos porque si alguien necesita tal volumen, seguramente tiene dinero para obtener un certificado de pago.

Automatic Certificate Management Environment (ACME)

ACME8 9 es un estándar creado para definir como autentificar que un dominio es quien dice ser y poder verificar los certificados ssl, sin esa verificación tus certificados no estarán en vigor y por lo tanto no validarán.

Para ello Let’s Encrypt hará una petición a nuestro dominio tal que domain.tdl/.well-known/acme-challenge/[random string] y comprobará que [random string] existe, y así certbot comprobará para proceder a la verificación de nuestro dominio.

Nginx

Instalación

Para instalar el servidor web Nginx tenemos la opción de compilarlo, pero considero que esto se sale del artículo y no es habitual necesitar instalarlo de este modo por lo que iremos directamente a instalarlo con el gestor de paquetes de nuestra distribución linux (ubuntu/debian en este caso), además instalaremos todas las dependencias que vamos a necesitar a lo largo del artículo:

$ apt-get install nginx openssl wget git vim
Iniciar, parar, reiniciar y recargar

Las diferencias no suelen estar muy claras entre reiniciar y recargar.
Reinciar: cuando reiniciamos (restart) un servicio, el sistema lo que hace es pararlo e iniciarlo.
Recargar: al recargar (reload) un servicio el sistema vuelve a cargar de nuevo todos los archivos de configuración sin parar el servicio en ningún momento.

Directorios y configuración

Nginx nos crea un subdirectorio en /etc llamado, valga la redundancia, nginx. De tal manera que toda la configuración que es estrictamente del servidor web se guarda en este subdirectorio.

Dentro de este subdirectorio tenemos los siguientes:

  • conf.d: archivos de configuración que se cargan siempre.
  • snippets: pedazos de configuración que luego vamos a incluir, o no, dentro de nuestras configuraciones personalizadas. Vamos a utilizar bastante este en este artículo.
  • sites-avaible: configuración de nuestros “virtualhost”, nuestros sitios dentro del servidor.
  • sites-enable: aquí ponemos un enlace simbólico a los archivos de configuración de nuestros sitios en sites-avaible qué queremos activar.
Configuración de un sitio estático

Vamos a configurar nuestro primer sitio estático, en este caso el hostname de este servidor es tristan.1k8b.com por lo que he creído conveniente poner una página estática ahí. En este caso Tristán lo he escogido porque es un nombre celta y no quería usar letras griegas o nada así muy típico. Bueno al lío.

En este caso tristan.1k8b.com es el servidor por defecto por lo que he puesto el contenido de este fichero en /etc/nginx/sites-avaible/default, acuerdate de hacer una copia de seguridad del anterior por el viejo truco de copiar ?:

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name _; #En este caso quiero que responda a todos los nombres de servidor que apunten a esta ip.

    ##
    # Static site
    ##

    charset utf-8;

    root /var/www/html/;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ index.html;
    }
}
  • server sirve para crear lo que con apache llamamos virtualhost, es para decirle cómo debe responder por http para cierto nombre que apunte a nuestra ip.
  • listen el puerto que debe de usar, además se pueden especificar otros parámetros pero no voy a indagar en esto, puedes ver la documentación oficial si quieres.
  • sever_name aquí defines tantos nombres de servidor como quieras (ten en cuenta que en la dns deben apuntar a la ip donde corres el servidor) separados por espacios, _ son todos los nombres que no hayan coincidido con otro server por ese mismo puerto. En este caso podríamos haber puesto: server_name tristan.1k8b.com y funcionaría, pero no cuando alguien tratará de intentar acceder directamente con la ip.
  • charset codificación por defecto de los ficheros de texto.
  • root es el directorio donde tenemos los archivos de nuestro sitio en el servidor.
  • index son los archivos que usará (en ese orden) para servir por defecto.
  • location nos permite definir comportamientos para ciertas rutas, en este caso / serán todas las rutas pues no hay otra definida y coincidirá con todas.
  • try_files intenta resolver la petición y en este caso si no puede le devolvemos un error 404.
Configuración de una aplicación node (Nginx como proxy)

Las aplicaciones node corren en otros puertos normalmente y, aunque, se puede correr una aplicación node en el puerto 80 o 443 directamente eso nos limitaría a una única aplicación. Por lo que está clara la funcionalidad de correr nginx como proxy para aplicaciones node.

Por otro lado, estamos configurando certificados y de esta manera podemos configurar los certificados directamente en Nginx centrando así la configuración de nuestro sitio en lo que se refiere a servir la aplicación.

Pues bien, una vez más, al lío. Usando la configuración anterior:

server {  
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    server_name _; #En este caso quiero que responda a todos los nombres de servidor que apunten a esta ip.

    ##
    # Static site
    ##

    charset utf-8;

    root /var/www/html/;
    index index.html index.htm;

    location / {
            proxy_redirect off;
            proxy_http_version 1.1;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $http_host;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-NginX-Proxy true;
            #proxy_cache /tmp/$my_proxy_pass;
            proxy_cache_valid 200 302 24h;
            proxy_cache_valid 404 60s;
            add_header X-Cache-Status $upstream_cache_status;
            proxy_cache_key sfs$request_uri$scheme;
            proxy_pass http://127.0.0.1:2368;
    }
}

No voy a explicar toda la configuración, podéis ver aquí lo que no explique: http://nginx.org/en/docs/http/ngxhttpproxy_module.html
En esta configuración se incluye la necesaria para que funcionen los websockets.

  • proxy_pass la url y puerto a nuestra aplicación node que esta en nuestra propia maquina por eso 127.0.0.1 y el puerto en el que corremos nuestra aplicación node 2368 en este caso que corresponde al puerto que usa por defecto Ghost.
Nginx como balanceador de carga o con un servidor backup

En mi caso tengo este blog (Ghost) con dos contenedores Docker en dos puertos diferentes para poder actualizarlo sin “downtime” (y funciona ?) y he cerrado esos puertos con iptables y abierto exclusivamente el 80 (http) y 443 (https) por lo que creo que no hay que explicar más acerca de la evidente funcionalidad de configurar nginx de este modo.

Para hacer esto vamos a usar el módulo upstream que nos permite configurar un grupo de servidores al que hacer referencia.

upstream bloggabicomes {  
    #ip_hash;
    keepalive 32;
    server 127.0.0.1:2368 max_fails=2 fail_timeout=5s;
    server 127.0.0.1:61338 down; # Always Down
    server 127.0.0.1:2369 backup;
}
  • ip_hash hace que siempre dirija las peticiones de una ip al mismo servidor (si está disponible) para así no cruzar peticiones, está descomentado porque en este caso no es compatible.
  • keepalive activa la cache para conexiones a un grupo de servidores. Las conexiones se guardan en caché para cada proceso (worker process).
  • server la dirección del servidor con (opcionalmente) el puerto y otros parámetros. Más información.

Esta configuración yo la pongo siempre antes de server {}, ignoro si puede o debe hacerse en otro sitio. El nombre del upstream debe ser único.

Luego para que esto funcione, en la configuración del proxy modificamos proxy_pass con:

proxy_pass http://bloggabicomes;  

Como ven es muy funcional ya que si cambiamos el puerto, añadimos más servidores o lo que sea, la modificación es más fácil de hacer.

Certbot / Letsencrypt

Aunque todavía no hemos llegado, antes debemos de saber unas cuantas cosas sobres los certificados generados con certbot (el programa para crear y validar los certificados de Let’s Encrypt).

Cuando generamos los certificados estos se guardan (obviando que es para este dominio) en /etc/letsencrypt/live/blog.gabi.com.es/. Lo cual nos facilita bastante la tarea.

Certbot crea otras carpetas, entre las que están un archivo de certificados, otra con un archivo de configuración para renovaciones, los certificados propiamente dichos y otra de archivos CSR (Certificate signing request).

Archivo Diffie-Hellman

El archivo de claves Diffie-Hellman según el estándar sería de 1024 bits pero no es recomendable menos de 2048, tarda un poco en generarse (Yo no pude generar uno de 4096 ya que me caducaba la sesión; lo sé hay formas de crearlo igualmente pero creo que 2048 es suficiente). Para generarlo:

$ openssl dhparam -out /etc/nginx/dhparams.pem 2048
Configuración ssl

Mozilla ha hecho una aplicación web que nos permite tener una buena configuración SSL para nuestro servidor web, luego hay que hacer pequeñas modificaciones. La aplicación está en github.io.

Uno de los puntos importantes son la cabecera HSTS y ssl_ciphers (más información), entre otros. Mi configuración no sigue la que recomienda mozilla al 100% ya que he tenido que ajustar varios parámetros para que sea un poco más seguro.

upstream bloggabicomes {  
    #ip_hash;
    keepalive 32;
    server 127.0.0.1:2368 max_fails=2 fail_timeout=5s;
    server 127.0.0.1:61338 down; # Always Down
    server 127.0.0.1:2369 backup;
}

server {  
    listen 80;
    listen [::]:80;

    server_name gabi.com.es www.gabi.com.es blog.gabi.com.es www.blog.gabi.com.es;

    location / {
        # Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
        return 301 https://$host$request_uri;
    }
}

server {  
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name gabi.com.es www.gabi.com.es blog.gabi.com.es www.blog.gabi.com.es;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/blog.gabi.com.es/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.gabi.com.es/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/blog.gabi.com.es/fullchain.pem;

    # Sesion
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # SSL 2 y 3 tienen fallos importantes de seguridad por lo que no los habilitamos.
    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_prefer_server_ciphers on;
    #ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!ECDHE-RSA-DES-CBC3-SHA:!ECDHE-ECDSA-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:!DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP';
    ssl_dhparam /etc/nginx/dhparams.pem;
    ssl_ecdh_curve secp384r1;

    # Necesario para la verificación OCSP
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 30s;

    # Algunas cabeceras que nos proporcionan seguridad
    add_header Strict-Transport-Security "max-age=10886400; includeSubDomains; preload";
    add_header x-xss-protection "1; mode=block" always;
    add_header x-frame-options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;

    ##
    # Solo un nombre de dominio
    ##
    if ($http_host != 'blog.gabi.com.es') {
        rewrite ^ https://blog.gabi.com.es$request_uri permanent;
    }


    # UTF-8 por defecto
    charset utf-8;

    # Default location is for proxy
    location / {
        # HSTS
        # ¿Por qué defino aquí otra vez los headers?
        # https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
        add_header Strict-Transport-Security "max-age=10886400; includeSubDomains; preload" always;

        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-NginX-Proxy true;
        proxy_cache_valid 200 302 24h;
        proxy_cache_valid 404 60s;
        add_header X-Cache-Status $upstream_cache_status;
        proxy_cache_key sfs$request_uri$scheme;
        proxy_pass bloggabicomes;
    }
}

Usar los comentarios para aclarar que es cada cosa.

Variables

Ahora vamos a hacer nuestra configuración un poco más reusable… Ya que todos los sitios que queramos servir vamos a hacerlo en un único servidor tenemos que tener una configuración que nos permita rehusarla. Y para ello vamos a hacer uso de las variables de nginx. Antes de nada aclarar que las variables no sirven para hacer macros y crear una super configuración para gobernarlas a todas (más información). De hecho no se pueden poner variables en todos los parámetros.

Para definir una variable debemos usar:

set $[var-name] "value";  

Ejemplo:

set $myvar "myvalue;  

Luego invocaremos esta variable donde nos interese con: $myvar

Snippets

Los snippets son pedazos de configuración comunes que rehusamos, ponemos nuestra configuración común a varios server{} (por ejemplo) en un archivo y luego incluimos esa configuración con include /etc/nginx/snippets/mycommon.conf;.

Vamos a crear unos cuantos que nos van a ser útiles. Y ya que estamos vamos a completar toda nuestra configuración anterior un poco más.

Snippet para ACME

Vamos a crear un snippet que llamaremos letsencrypt-acme-challenge.conf con el siguiente contenido:

location ^~ /.well-known/acme-challenge/ {  
    # La especificación actual de ACME dice que "text/plain" para los archivos
    # podemos no poner nada per "text/plain" parece más seguro.
    default_type "text/plain";

    # El directorio que le diremos a letsencrypt donde meter los archivos para la verificación (ACME) del dominio
    root         /var/www/letsencrypt;
}

# Escondemos el subdirectorio /acme-challenge
# Es solo por seguridad
location = /.well-known/acme-challenge/ {  
    return 404;
}

Todo esto lo saqué de esta web: https://community.letsencrypt.org/t/how-to-nginx-configuration-to-enable-acme-challenge-support-on-all-http-virtual-hosts/5622

He acortado mucho los comentarios porque ya es bastante grande esta página.

Snippet para Proxy

Seguimos creando un snippet para el proxy en el que pondremos una variable en proxy_pass, también podemos omitila y ponerla luego a mano tras nuestro include (o delante, da igual). A este archivo lo nombre nodeproxy.conf

# set $nodeapp "http://ip:port";
# o
# set $nodeapp "http://myupstream";

proxy_redirect off;  
proxy_http_version 1.1;  
proxy_set_header X-Real-IP $remote_addr;  
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  
proxy_set_header X-Forwarded-Proto $scheme;  
proxy_set_header Host $http_host;  
proxy_set_header Upgrade $http_upgrade;  
proxy_set_header Connection 'upgrade';  
proxy_cache_bypass $http_upgrade;  
proxy_set_header X-NginX-Proxy true;  
#proxy_cache /tmp/$my_proxy_pass;
proxy_cache_valid 200 302 24h;  
proxy_cache_valid 404 60s;  
add_header X-Cache-Status $upstream_cache_status;  
proxy_cache_key sfs$request_uri$scheme;  
proxy_pass $nodeapp;  
Snippet para HTTPS

Toda la configuración https tiene muchas cosas en común y sabiendo que Let’s Encryp carga los ficheros en un subdirectorio con el nombre de dominio sería muy “cool” poder poner una variable en los ssl_certificate, ssl_certificate_key y ssl_trusted_certificate pero por desgracia estos tres valores son literales, igualmente hay mucha configuración común como los ssl_ciphers por poner un ejemplo y que puede interesarnos modificar más adelante. Con este snippet nos evitaremos modificar en varios sitios lo mismo.

ssl_session_timeout 10m;  
ssl_session_cache shared:SSL:10m;  
ssl_session_tickets off;  
charset utf-8;

ssl_protocols TLSv1.2 TLSv1.1 TLSv1;  
ssl_prefer_server_ciphers on;  
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!ECDHE-RSA-DES-CBC3-SHA:!ECDHE-ECDSA-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:!DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP';  
ssl_dhparam /etc/nginx/dhparams.pem;  
ssl_ecdh_curve secp384r1;


ssl_stapling on;  
ssl_stapling_verify on;

resolver 8.8.8.8 8.8.4.4 valid=300s;  
resolver_timeout 30s;

# Headers
include /etc/nginx/snippets/https-headers.conf;  
Snippet para cabeceras comunes

Debido a que dentro de los Location en nuestros archivo de configuración debemos de redefinir las cabeceras he decidido separarlas en un archivo: https-headers.conf.

add_header Strict-Transport-Security "max-age=10886400; includeSubDomains; preload";  
add_header x-xss-protection "1; mode=block" always;  
add_header x-frame-options "DENY" always;  
add_header X-Content-Type-Options "nosniff" always;  
Snippet para directorios

Si configuras todo como dije anteriormente verás que los logs se te llenan de, digamos, “basura” y que guardarás un log de accesos a imágenes, css, archivos javascript… Además está bien aclararle al servidor que no debe acceder a archivos ocultos, por eso vamos a poner también esta configuración en el snippet locations-for-proxy.conf:

# Directives to send expires headers and turn off 404 error logging.
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg)$ {  
    expires max;
    log_not_found off;
    access_log off;
    include /etc/nginx/snippets/nodeproxy.conf;
}

location = /favicon.ico {  
    log_not_found off;
    access_log off;
    include /etc/nginx/snippets/nodeproxy.conf;
}

location = /robots.txt {  
    allow all;
    log_not_found off;
    access_log off;
    include /etc/nginx/snippets/nodeproxy.conf;
}

## Disable viewing .htaccess & .htpassword
location ~ /.ht {  
    deny  all;
}
¡Mira qué configuración tan mona!

Ahora vamos a poner nuestra configuración molona, en mi caso he creado un archivo en la ruta /etc/nginx/sites-available/bloggabicomes.conf y he hecho un enlace simbólico al archivo:

$ cd /etc/nginx/sites-enabled && ln -s ../sites-available/bloggabicomes.conf bloggabicomes

Con el contenido, ahora mucho más limpio y legible:

upstream bloggabicomes {  
    keepalive 32;
    server 127.0.0.1:2368 max_fails=2 fail_timeout=5s;
    server 127.0.0.1:2369 backup;
}

server {  
    listen 80;
    listen [::]:80;

    server_name gabi.com.es www.gabi.com.es blog.gabi.com.es www.blog.gabi.com.es;

    location / {
        # Redirigimos todas las peticiones http al mismo dominio pero
        # con https, si redirigiéramos directamente a
        # blog.gabi.com.es daría error luego al añadirlo a la lista de google chrome
        return 301 https://$host$request_uri;
    }

    ##
    # ACME Config location for letsencrypt
    ##

    include /etc/nginx/snippets/letsencrypt-acme-challenge.conf;
}

server {  
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name gabi.com.es www.gabi.com.es blog.gabi.com.es www.blog.gabi.com.es;

    set $https_default_domain "blog.gabi.com.es";
    set $nodeapp http://bloggabicomes;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/blog.gabi.com.es/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.gabi.com.es/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/blog.gabi.com.es/fullchain.pem;
    include /etc/nginx/snippets/letsencrypt-https.conf;


    ##
    # Redirection to default server
    ##
    if ($http_host != $https_default_domain) {
        rewrite ^ https://$https_default_domain$request_uri permanent;
    }


    # Default location is for proxy
    location / {
        # Headers
        include /etc/nginx/snippets/https-headers.conf;

        # Proxy
        include /etc/nginx/snippets/nodeproxy.conf;
    }

    # Other useful locations
    include /etc/nginx/snippets/locations-for-proxy.conf;


    ##
    # ACME Config location for letsencrypt
    ##

    include /etc/nginx/snippets/letsencrypt-acme-challenge.conf;
}

La configuración ACME no es necesaria usando https, pero no molesta ?.

Obteniendo Certbot

No podremos generar los certificados si no obtenemos primero Certbot, para ello vamos a ejecutar:

$ git clone https://github.com/certbot/certbot /opt/letsencrypt
$ /opt/letsencrypt/letsencrypt-auto --help

Lo que haremos con esto es descargarnos de github el script/aplicación para poder generar y verificar los certificados en /opt/letsencrypt. Posteriormente el segundo comando nos muestra la ayuda, es para verificar que todo está bien, nada más.

Generar certificados

Por fin vamos a generar los certificados, pero antes hay que tener en cuenta que podemos generar uno o varios certificados. Si generamos uno al tener la misma ip varios certificados instalados, los clientes (navegadores web) tienen que tener soporte para SNI.
Y si generamos varios certificados, el principal será el primero que especifiquemos y la ruta de configuración (si el primero es example.com) para el certificado de todos los dominios será /etc/letsencrypt/live/example.com/fullchain.pem.

Para generar los certificados debido a que tenemos nginx corriendo vamos a usar el plugin webroot:

/opt/letsencrypt/letsencrypt-auto certonly --renew-by-default -a webroot --webroot-path=/var/www/letsencrypt --agree-tos --staple-ocsp --hsts -d blog.gabi.com.es -d www.blog.gabi.com.es -d gabi.com.es -d www.gabi.com.es -d tristan.1k8b.com

Con este comando generaremos un certificado para todos los dominios especificados y se guardará en: /etc/letsencrypt/live/blog.gabi.com.es/. En realidad nos crea varios archivos pero no voy a hablar de ello porque en la ayuda lo hace muy bien.

Debemos ver un mensaje parecido a:

IMPORTANT NOTES:  
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live-ecdsa/blog.gabi.com.es/0001_chain.pem. Your
   cert will expire on 2016-11-26. To obtain a new or tweaked version
   of this certificate in the future, simply run letsencrypt-auto
   again. To non-interactively renew *all* of your certificates, run
   "letsencrypt-auto renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
Sacando la configuración SSL del server{}

Si solo vamos a emplear un certificado (en la última versión nginx se pueden usar varios pero no es para esto) para todos los dominios de nuestro servicio nginx (todos los server{}) podemos sacar la configuración ssl fuera de nuestro server{} y la podemos poner en un archivo en el directorio conf.d haciendo así más ligeros aún nuestros archivos de configuración y haciendo que se repitan menos algunas líneas de configuración.

Comprobando los archivos de configuración

Para comprobar que los archivos de configuración Nginx son correctos debemos usar el parámetro -t, es casi obligatorio hacerlo antes de iniciar, reiniciar o recargar el servicio nginx ya que todo puede fallar:

$ nginx -t
Poniendo nuestra web en marcha

Una vez comprobados los archivos de configuración solo nos queda re/iniciar nginx o recargarlo si está funcionando (así no tendrás downtime).

$ /etc/init.d/nginx restart

La recomendación que yo hago es siempre, reiniciar, si puedes.

Comprobando la calidad de nuestros certificados

Una vez nuestros certificados están instalados, lo primero obviamente es entrar en la web, después disponemos de varias herramientas para comprobar la calidad de nuestro certificado:
https://www.ssllabs.com/ssltest/
https://ssldecoder.org

Renovar los certificados automáticamente

Hay muchos scripts para renovar los certificados con Let’s Encrypt, pero actualmente cerbot ya facilita el proceso ya que simplemente ejecutando (más información):

/opt/letsencrypt/letsencrypt-auto renew

Renovará TODOS los certificados que hayamos creado en esa máquina. Por lo que si yo generó uno para blog.gabi.com.es y luego otro para www.blog.gabi.com.es y luego quiero juntarlos tendré que borrar el archivo de configuración apropiado en /etc/letsencrypt/renew, sino seguirá generando los certificados por separado o peor verificará uno, luego otro y quedará revocado el que se verificó en primer lugar.

Ahora para automatizarlo tan solo debemos añadir el comando a nuestro crontab, yo he decidido que como caduca cada 3 meses se ejecute 1 vez al mes (a las 3 a.m. los domingos):

$ crontab -e

Y añadimos las siguientes líneas (una es para que se actualice mensualmente también letsencrypt; lo hará una hora antes que los certificados, a las 2 a.m.):

MAILTO="youremail@example.com"  
0 3 * * 0 /opt/letsencrypt/letsencrypt-auto renew  
0 2 * * 0 cd /opt/letsencrypt && git pull >> ~/update-letsencrypt.log  

MAILTO: será el correo al que nos enviará todo el stdout de ejecutar los comandos.
Luego los 5 primeros números espaciados definen cuando se ejecuta el comando, por orden son:

  • Minuto de 0 a 59, * son todos.
  • Hora de 0 a 23, * todas las horas.
  • Día del mes 0 a 31, * todos los días.
  • Mes del año de 0 a 12, * son todos.
  • Día de la semana 0 a 6, 0 es domingo y * todos los días.+
  • Comando a ejecutar propiamente dicho.

También podemos usar fracciones. Por ejemplo en los minutos */4 sería cada 15 minutos.

HSTS Segundo asalto

Si ha seguido toda la configuración, tiene que tener HSTS habilitado. Esto se hace para que su sitio solo sea accesible por https, para ello Google ha habilitado un formulario de alta a la lista HSTS. Actualmente solo la utiliza Google Chrome pero es recomendable si quieres que tu sitio solo sea accesible mediante https.
https://hstspreload.appspot.com/

Lo que falta

Después de actualizar los certificados es necesario reiniciar nginx, pero esto lo veremos en otro artículo y lo enlazaré enlazo aquí tan pronto como esté disponible. Ya que haremos que se reinicie siempre que nuestros certificados cambien.

ACTUALIZO CON ENLACE

https://blog.gabi.com.es/2016/09/09/monitorizar-directorios-ejecutar-comandos/

Conclusiones

Puede parecer un poco complicado al principio pero finalmente creo que no es tan complicado instalar de manera básica un certificado SSL, por supuesto que hay que tener en cuenta el precio y también depende de donde queramos instalarlo.
Si estás usando un hosting compartido existe un listado de proveedores de hosting que posibilitan hacer todo esto en tú espacio para tú web de forma ágil y visual (1 click), el listado es este:

https://community.letsencrypt.org/t/web-hosting-who-support-lets-encrypt/6920

ACTUALIZACIÓN 2

Puede que te interese configurar otros servicios con su propio certificado ssl. Si ese es el caso en el enlace que dejo a continuación, puedes ver varias (ftp, ssh, correo…):
https://cipherli.st/


Es inviable que ponga absolutamente todas las fuentes porque he consultado innumerables sitios de los que no guardo enlace en muchos casos (han sido demasiados), aquí los que he podido recuperar o algunos que tenía en mis Favoritos; también algunos que pueden ser útiles: