Por qué no usar el servidor de desarrollo en producción
El servidor integrado de Flask (flask run / app.run(debug=True)) está diseñado exclusivamente para desarrollo. En producción presenta limitaciones críticas:

- Un solo proceso, un solo hilo: solo puede manejar una petición a la vez
- Sin recuperación ante fallos: si el proceso cae, la aplicación deja de funcionar
- Sin gestión de recursos: no limita conexiones, memoria ni tiempo de respuesta
- Modo debug activado: expone información sensible del sistema en las páginas de error
- Sin logging de producción: no registra peticiones de forma estructurada
# NUNCA en producción:
if __name__ == '__main__':
app.run(debug=True) # Solo para desarrollo local
# En producción, el punto de entrada es solo la instancia de la app:
# gunicorn app:app
Gunicorn: el servidor WSGI de producción
Gunicorn (Green Unicorn) es el servidor WSGI más popular para aplicaciones Flask en producción. Es estable, bien mantenido y fácil de configurar:
pip install gunicorn
Estructura básica de la aplicación para Gunicorn:
# app.py
from flask import Flask
def crear_app(config_name='production'):
app = Flask(__name__)
app.config.from_object(f'config.{config_name.title()}Config')
# Inicializar extensiones
from app.extensions import db, migrate, login_manager
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# Registrar blueprints
from app.blueprints.api import api_bp
from app.blueprints.web import web_bp
app.register_blueprint(api_bp, url_prefix='/api')
app.register_blueprint(web_bp)
return app
# Punto de entrada para Gunicorn
app = crear_app()
Ejecutar con Gunicorn:
# Forma básica
gunicorn app:app
# Con múltiples workers (recomendado: CPU * 2 + 1)
gunicorn -w 4 app:app
# Con host y puerto específicos
gunicorn -w 4 -b 0.0.0.0:5000 app:app
# Para app factory pattern
gunicorn -w 4 'app:crear_app()'
# Con logging a archivo
gunicorn -w 4 -b 0.0.0.0:5000 \
--access-logfile /var/log/gunicorn/access.log \
--error-logfile /var/log/gunicorn/error.log \
app:app
Configuración de Gunicorn con archivo de configuración
Para entornos de producción, es mejor usar un archivo de configuración de Gunicorn:
# gunicorn.conf.py
import multiprocessing
import os
# Dirección de escucha
bind = '127.0.0.1:5000'
# Número de workers (2-4 x num_cores)
workers = multiprocessing.cpu_count() * 2 + 1
# Tipo de worker (sync para Flask estándar, gevent o uvicorn para async)
worker_class = 'sync'
# Timeout de petición (segundos)
timeout = 30
# Mantener conexiones abiertas
keepalive = 5
# Máximo de peticiones por worker (evita memory leaks)
max_requests = 1000
max_requests_jitter = 100
# Logging
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'warning'
# Recarga automática en desarrollo (NO en producción)
reload = os.environ.get('FLASK_DEBUG', '0') == '1'
# PID file
pidfile = '/var/run/gunicorn/gunicorn.pid'
Usar el archivo de configuración:
gunicorn -c gunicorn.conf.py app:app
Configuración de la aplicación para producción
# config.py
import os
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY')
SQLALCHEMY_TRACK_MODIFICATIONS = False
@classmethod
def init_app(cls, app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///dev.db'
class ProductionConfig(Config):
DEBUG = False
TESTING = False
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
# Configuraciones de seguridad de producción
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
PREFERRED_URL_SCHEME = 'https'
@classmethod
def init_app(cls, app):
Config.init_app(app)
# Logging a syslog en producción
import logging
from logging.handlers import SysLogHandler
syslog_handler = SysLogHandler()
syslog_handler.setLevel(logging.WARNING)
app.logger.addHandler(syslog_handler)
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
Variables de entorno en producción (archivo .env o variables del sistema):
# .env (producción - NUNCA subir a git)
SECRET_KEY=tu-clave-super-secreta-de-64-caracteres-minimo
DATABASE_URL=postgresql://usuario:password@localhost:5432/miapp
FLASK_DEBUG=0
REDIS_URL=redis://localhost:6379/0
MAIL_SERVER=smtp.sendgrid.net
MAIL_USERNAME=apikey
MAIL_PASSWORD=tu-api-key-sendgrid
Nginx como proxy inverso
Nginx actúa como proxy inverso delante de Gunicorn, manejando conexiones TLS, archivos estáticos y equilibrio de carga:
# /etc/nginx/sites-available/miapp
server {
listen 80;
server_name miapp.com www.miapp.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name miapp.com www.miapp.com;
# Certificados SSL (Let's Encrypt / Certbot)
ssl_certificate /etc/letsencrypt/live/miapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/miapp.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
# Archivos estáticos servidos directamente por Nginx (más eficiente)
location /static {
alias /var/www/miapp/static;
expires 30d;
add_header Cache-Control "public, no-transform";
}
# Proxy a Gunicorn
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $http_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_connect_timeout 10s;
proxy_read_timeout 30s;
# Límites de tamaño
client_max_body_size 16m;
}
}
Flask debe estar configurado para confiar en los headers del proxy:
# app.py
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
# En producción con Nginx, corregir los headers de proxy
app.wsgi_app = ProxyFix(
app.wsgi_app,
x_for=1, # Número de proxies que añaden X-Forwarded-For
x_proto=1, # Número de proxies que añaden X-Forwarded-Proto
x_host=1, # Número de proxies que añaden X-Forwarded-Host
)
Systemd: gestión del proceso
Configura Gunicorn como servicio systemd para que inicie automáticamente y se reinicie ante fallos:
# /etc/systemd/system/miapp.service
[Unit]
Description=Gunicorn para Flask MiApp
After=network.target
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/miapp
Environment="PATH=/var/www/miapp/venv/bin"
EnvironmentFile=/var/www/miapp/.env
ExecStart=/var/www/miapp/venv/bin/gunicorn \
-c /var/www/miapp/gunicorn.conf.py \
app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
Comandos de gestión del servicio:
# Activar y arrancar el servicio
sudo systemctl enable miapp
sudo systemctl start miapp
# Verificar estado
sudo systemctl status miapp
# Ver logs
sudo journalctl -u miapp -f
# Recargar configuración sin parar el servicio
sudo systemctl reload miapp
# Reiniciar el servicio
sudo systemctl restart miapp
Recarga sin tiempo de caída (zero-downtime)
Gunicorn soporta recarga en caliente enviando la señal SIGHUP, que crea nuevos workers con el código actualizado antes de terminar los anteriores:
# Obtener el PID de Gunicorn
cat /var/run/gunicorn/gunicorn.pid
# Recargar workers sin interrumpir peticiones en curso
kill -HUP $(cat /var/run/gunicorn/gunicorn.pid)
# O usando systemctl
sudo systemctl reload miapp
Este enfoque garantiza que las peticiones en curso se completen antes de reemplazar los workers, consiguiendo zero-downtime deployments.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Flask
Documentación oficial de Flask
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Flask es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Flask
Explora más contenido relacionado con Flask y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender por qué no se debe usar el servidor de desarrollo de Flask en producción. Configurar Gunicorn como servidor WSGI de producción para Flask. Usar Nginx como proxy inverso delante de Gunicorn. Gestionar variables de entorno y configuraciones de producción de forma segura. Configurar el número de workers y opciones de rendimiento de Gunicorn.