NGINX + SSL, python + uWSGI en Docker

Hola a tod@s!

Antes de ver este post, si no has visto el post anterior, te lo recomiendo para seguir un poco la estructura:
https://tomonota.net/configuracion-uwsgi-nginx-flask/

En este post veremos como añadir los certificados SSL Let’s Encrypt a nuestra web además de actualizarse automáticamente cada 3 meses. También dejo el usar dos rutas en nginx ya que siempre nos puede venir bien en el caso de que tengamos una web principal y por otra parte una api para el front-end por ejemplo.

¿Y porqué no todo junto en el mismo post?

Porque quería dedicar un post único a esto de los certificados, creo que es una manera de ir paso a paso y si además no funciona lo anterior, no sigáis con esto, primero tenemos que hacer que funcione lo de antes y luego pasar a los certificados.

Estructura del proyecto:

 mi_proyecto/ 
├── docker-compose.yml 
├── app/ 
           └── app_api.py 
├── infrastructure/
      ├── app_python/
           └── requirements.txt
           └── Dockerfile
           └── uwsgi.ini
      ├── config_nginx/
           └── app_python/nginx.conf
├── nginx_logs/ 
           └── access.log
           └── error.log 
├── .env
├── .gitignore
├── README

Seguiremos con el docker-compose, añadiendo/modificando los siguientes datos:

  nginx:
    image: nginx
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./infrastructure/config_nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./certbot-conf:/etc/letsencrypt  #Nuevo
      - ./nginx_logs:/var/log/nginx
    networks:
      - shared_network
    depends_on:  #Nuevo
      - certbot  #Nuevo
  certbot: # El que nos lo actualizará automáticamente
    image: certbot/certbot
    volumes:
      - ./certbot-conf:/etc/letsencrypt
      - ./infrastructure/config_nginx:/etc/nginx
    command: ['renew', '--webroot', '-w', '/etc/letsencrypt', '--quiet']

Pasamos a la parte del NGINX:
Ccomo os comentaba antes, está con la opción de reenviar todo lo que entre por el puerto 80 al puerto 443 además de tener varios reenvíos, me refiero a tener una web principal y por otro lado una api:

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    sendfile on;
    
    upstream app_server {
        # Dirección y puerto del servidor uWSGI.
        server python_container:4450;
    }


    server {
        # Esta sección es para redirigir todas las conexiones no seguras (HTTP, puerto 80) a HTTPS (puerto 443).
        listen 80;
         server_name tu_dominio.com;

        # Necesario para el certbot!
        
        location ^~ /.well-known/acme-challenge/ {
            default_type "text/plain"; #  Tipo MIME, respuesta "text/plain".
            root /etc/letsencrypt; # Donde se buscan los certificados
            allow all; # Permite el acceso a todas las ip
            try_files $uri =404; # Si no encuentra los certificados da un error 404
        }

        location ~ {
            return 301 https://$host$request_uri;
      
        }
        # Carpeta de registros
        access_log /var/log/nginx/access.log;  
        error_log /var/log/nginx/error.log;
    }
    
    server {
        listen 443 ssl;
        server_name tu_dominio.com;

        ssl_certificate /etc/letsencrypt/live/tu_dominio.com/fullchain.pem; # Especifica la ubicación de tu certificado
        ssl_certificate_key /etc/letsencrypt/live/tu_dominio.com/privkey.pem; # Especifica la ubicación de tu clave privada
        
        location / {
        # Redirige las solicitudes a tu aplicación web
        proxy_pass http://nombre_servicio_contenedor_webPrincipal:5173;
        
        # Configuraciones adicionales... (ejemplo lo de abajo)
        }
        

        location /api {

            #Sobreescribe el /api y te lo deja como / para que no se añada en Flask y lee directamente de raiz (para consultar a la api será: tu_dominio.com/api/xxx).
            rewrite ^/api(.*)$ $1 break;

            # Se usa con uwsgi
            include uwsgi_params;
            uwsgi_pass app_server;

            # Establece el encabezado Host para el proxy.
            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 Content-Type $http_content_type;
            proxy_set_header Content-Length $http_content_length;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;

            proxy_set_header Connection "Upgrade";

            # Control de manejo del tamaño de archivo para enviar.
            client_max_body_size 50M; 
            
            # Encabezados Cors, permite que el navegador realice las solicitudes
            # Si deseas restringir el acceso ejemplo:
            # add_header Access-Control-Allow-Origin http://ip_publica:20222;

            # Permitir el acceso a todos.
            add_header Access-Control-Allow-Origin *;
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';            
            add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';  
            add_header Access-Control-Expose-Headers 'Content-Length,Content-Range';
        }
        # Carpeta de registros
        access_log /var/log/nginx/access.log;  
        error_log /var/log/nginx/error.log;
    }
}

He dejado marcado en negrita lo que tenéis que tener en cuenta de cambiar el nombre:
– tu_dominio.com
– puertos
– nombres de los servicios de docker

Una vez tenemos esto configurado, antes de lanzarlo hay que configurar letsencrypt!
*IMPORTANTE
Para poder configurar el certificado, lanzaremos la web mediante el puerto 80.
Esto se hace porque para que certbot se configure, intentará acceder a la web y si la web no funciona o tiene algún tipo de error no se configura y dará error.
Para lanzar únicamente con el puerto 80 (sin ssl) comentaremos todo lo relacionado con el 443, veamos la parte del código que tenemos que comentar:

server {
        listen 80;
         server_name tu_dominio.com;
        
        location ^~ /.well-known/acme-challenge/ {
            default_type "text/plain"; #  Tipo MIME, respuesta "text/plain".
            root /etc/letsencrypt; # Donde se buscan los certificados
            allow all; # Permite el acceso a todas las ip
            try_files $uri =404; # Si no encuentra los certificados da un error 404
        }
        ##!!!!! COMENTAMOS DESDE AQUÍ !!!!!!!!!!!!!!!!!
        location ~ {
            return 301 https://$host$request_uri;
        }

        *# LOS REGISTROS SI QUEREIS LO DEJAMOS DESCOMENTADO PARA VER EL LOG.
        access_log /var/log/nginx/access.log;  
        error_log /var/log/nginx/error.log;
    #} -> ESTO TAMBIÉN COMENTAMOS
    
    server {
        listen 443 ssl;
        server_name tu_dominio.com;

        ssl_certificate /etc/letsencrypt/live/tu_dominio.com/fullchain.pem; # Especifica la ubicación de tu certificado
        ssl_certificate_key /etc/letsencrypt/live/tu_dominio.com/privkey.pem; # Especifica la ubicación de tu clave privada
        
        ##!!!!! COMENTAMOS HASTA AQUÍ !!!!!!!!!!!!!!!!! 

        location / {
        # Redirige las solicitudes a tu aplicación web
        proxy_pass http://nombre_servicio_contenedor_webPrincipal:5173;
        
        # Configuraciones adicionales... (ejemplo lo de abajo)
        }

Lo que hemos comentado es la parte conexión segura mediante el puerto 443 dejando libre la conexión por el puerto 80.
1º Arrancamos la web, 2º Arrancamos nginx y 3º Ejecutamos el primer comando de certbot para que descargue los certificados, esto es algo que se tiene que hacer únicamente la primera vez (o en el caso de que se tenga que actualizar manualmente.

*Nota: Para arrancar únicamente un servicio podemos decirle el comando: docker-compose up nginx y los servicios que quieras.

El comando para inicializar certbot es:

  • Desde dentro del contenedor:
certbot certonly --webroot -w /etc/letsencrypt --email tu@email.es --agree-tos --no-eff-email -d tu_dominio.com
  • Desde fuera del conetedor:
docker-compose run --rm --entrypoint "certbot certonly --webroot -w /etc/letsencrypt --email mtu@email.es --agree-tos --no-eff-email -d tu_dominio.com" certbot

Si nos confirma el resultado ya podremos detener lo actual y luego hacer docker-compose up -d para levantar todos los servicios.


Como siempre, espero que estas notas os sirvan de ayuda.

Salu2!

Leave Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *