Configurar Nginx como Reverse Proxy + Certbot con Docker

Configurar Nginx como Reverse Proxy + Certbot con Docker

Si somos de los que gusta trastear con estas cosas, es probable que en algún momento decidamos dar el salto a tener nuestro propio servidor, ya sea en casa o en la nube. En cualquier caso, es también probable que llegados a ese punto queramos probar varios servicios por nuestra cuenta: nuestra propia página web, nuestro propio NextCloud, nuestro blog, nuestra herramienta para gestionar nuestras recetas...

Las posibilidades son muchas, y más gracias al OpenSource (tema sobre el que en algún momento hablaremos), por lo que después de montar nuestro servidor, y empezar a desplegar servicios, tal vez queramos hacernos con nuestro propio dominio para tener "el pack completo". Ahí es donde tener un Reverse Proxy nos puede ayudar a gestionar todo eso.

¿Qué es un Reverse Proxy?

Tampoco vamos a entrar muy en detalle sobre esto (en Internet hay miles de páginas donde se explica perfectamente), pero a modo de resumen podemos imaginarlo como un "distribuidor", es decir, un señor que se encarga de indicar dónde está cada cosa. Sería como un DNS, pero a nivel interno, así para hacer un símil muy cutre.

En la imagen superior vemos una representación de cómo funcionaría: el navegador (nosotros) busca la url "midominio.com/blog", las DNS de internet lo llevan a nuestro servidor, y cuando llega se encuentra al señor Nginx esperando en la puerta para pedirle que a dónde va. Nginx, al ser preguntado por una dirección que conoce, le indica al navegador qué camino seguir para llegar hasta ella.

Fácil, rápido y para toda la família.

Configuración

Hay que tener en cuenta que para que todo funcione correctamente, lo ideal es que nuestro servidor tenga únicamente abiertos los puertos web 80 y 443, ya que esas serán las únicas "puertas" por las que se podrá acceder (preferiblemente siempre por el 443).

Además, lo suyo es que también hayamos añadido en nuestro proveedor de dominios tantos subdominios como queramos (en caso de necesitarlos) y apuntar correctamente esos dominios y subdominios a la IP pública de nuestro servidor. Esto depende de cada proveedor, así que el cómo hacerlo lo tendréis que consultar vosotros.

Y ya para poder empezar con la configuración, creamos en nuestro servidor una carpeta llamada nginx, por ejemplo, donde más nos guste y dentro creamos otras 2 carpetas, una llamada conf.d y otra llamada passwd.

Dockerfile

Lo siguiente que necesitamos es el Dockerfile que se encargará de preparar la imagen de Nginx. Creamos un fichero llamado Dockerfile con el siguiente contenido:

FROM nginx

RUN apt-get update && apt-get install -y \
software-properties-common && add-apt-repository "deb http://deb.debian.org/debian stretch-backports main" && \
apt-get update && apt-get install -y \
certbot python-certbot-nginx -t stretch-backports

Nota: El comando RUN debe ejecutarse como si fuera una única instrucción. Si al copiar/pegar no os interpreta bien los saltos de línea (\), los elimináis y lo dejáis todo en una única línea.

docker-compose.yml

Luego creamos, en esa misma carpeta, un fichero llamado docker-compose.yml y añadimos lo siguiente:

version: '3'
services:
  web:
    build: .
    container_name: nginxReverseProxyHttps
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./conf.d/:/etc/nginx/conf.d
      - ./passwd/:/etc/nginx/passwd
      - ./letsencrypt/:/etc/letsencrypt/
      - ./certificates/certs/:/etc/nginx/certs
      - ./certificates/private/:/etc/nginx/private
      - ./logs/:/var/log/nginx/

Los volúmenes a las carpetas letsencrypt y certificates son para tener en nuestra máquina local los certificados que se generarán con Certbot (explicado más abajo), la carpeta logs nos permite consultar los logs que genere Nginx, la carpeta conf.d va a contener el fichero que crearemos a continuación, y la carpeta passwd la vamos a usar por si queremos añadir "Basic Auth" a nuestros dominios (también explicado más abajo).

default.conf

Para añadir la configuración de cada dominio o subdominio, debemos crear un fichero llamado default.conf dentro de la carpeta llamada conf.d, con el siguiente contenido, dependiendo de cómo tengamos los dominios:

## BASIC REVERSE PROXY SERVER ##
################################


server {
    server_name  midominio.com;
    
    location / {
        proxy_pass http://IP:PORT;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $server_name;
    }

    location /page1 {
        proxy_pass http://IP:PORT;
        proxy_set_header Host $host;
        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 X-Forwarded-Host $server_name;
        proxy_set_header X-NginX-Proxy true;
    }

}

server {
    server_name  subdominio.midominio.com;

    location / {
        proxy_pass http://IP:PORT;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $server_name;
    }

}

Lo que hay que tener en cuenta aquí es que cada bloque de "server" tiene su propia configuración, y debería corresponder a 1 por dominio/subdominio. En el caso del primer bloque, vemos que podemos añadir configuración extra a "subpáginas" que pertenezcan a ese dominio.

Donde pone IP deberemos poner la IP de nuestro servidor, y en PORT el puerto donde esté escuchando el servicio al que corresponde el dominio/subdominio/subpágina.

En cuanto a la configuración de los "proxy_set...", esta es una configuración básica, pero es posible que dependiendo del servicio que se vaya a usar se requiera de otro tipo de configuración, por lo que se deberá recurrir a la documentación oficial del propio servicio.

Securizar con basic auth

Si queremos añadir un poco de seguridad, podemos hacerlo creando un fichero llamado .htpasswd dentro de la carpeta llamada passwd que hemos creado antes, y en él se deben guardar tantos usuarios como se quiera, con su contraseña "encriptada", de la siguiente manera:

user1:pass1
user2:pass2

Para encriptar la contraseña se puede usar el siguiente comando:

openssl passwd -apr1 PASSWORD

Y si no tenemos instalado openssl, hay muchas webs que permiten generar este tipo de ficheros.

Para securizar los dominios/endpoints se debe añadir lo siguiente a la configuración de nginx, dependiendo de qué se quiera securizar:

server {
    server_name  DOMAIN_NAME;
    ...
    auth_basic "Password Required";
	auth_basic_user_file /etc/nginx/passwd/.htpasswd;
    ...
	
    location / {
        ...
        auth_basic "Password Required";
        auth_basic_user_file /etc/nginx/passwd/.htpasswd;
        ...
    }

    location /service1 {
        ...
        auth_basic "Password Required";
        auth_basic_user_file /etc/nginx/passwd/.htpasswd;
        ...
    }
}

Nota: añadir sólo las directrices "auth_basic" donde lo necesitemos.

Con esto, cuando accedamos al dominio en cuestión veremos algo como esto:

Ponerlo todo en marcha

Una vez que tenemos todo preparado, ejecutamos el siguiente comando:

docker-compose up -d

Con esto tenemos ya nuestro servidor Nginx escuchando en los puertos 80 y 443 para empezar a dirigir el tráfico.

Lo que nos falta ahora es crear los certificados para poder usar una conexión HTTPS.

Crear certificados con Certbot

Para generar los certificados, accederemos al contenedor que se habrá creado al ejecutar el comando anterior, que en este caso es nginxReverseProxyHttps. Para ello, ejecutamos:

docker exec -it nginxReverseProxyHttps bash

De esta manera entraremos dentro del contenedor y podremos ejecutar lo siguiente:

certbot --nginx

Con esto nos aparecerá una lista con todos los dominios y subdominios que hayamos añadido al fichero default.conf, y elegimos el/los que queremos generar:

Y cuando nos pregunte si queremos forzar la redirección a HTTPS elegimos que sí (la opción 2):

Ahora sólo queda reiniciar el contenedor:

docker-compose restart

Y ya estaría. Si todo ha salido a pedir de milhouse, ya tenemos nuestro servidor listo para servir peticiones HTTPS para nuestros servicios.

Renovar certificados

Sí, los certificados son gratis (aunque los proveedores de dominio nos quieran cobrar por ellos), pero caducan cada cierto tiempo. Para evitar eso podemos añadir una tarea a Cron para que se renueven, por ejemplo, cada 30 días.

Simplemente ejecutamos:

crontab -e

Y añadimos la siguiente línea al final del archivo:

* 2 1 * * docker exec -d nginxReverseProxy certbot renew

Con esto actualizaremos los certificados cada 1º de mes, a las 2 de la mañana.

Y ahora sí, ya lo tenemos todo listo. Ale, a empezar a trastear.