How to Deploy a Flask Application with Gunicorn and Nginx on RHEL 7
Flask is a lightweight Python web framework ideal for building APIs and web applications. While Flask’s built-in development server is useful for testing, it is not suitable for production workloads. The standard production stack on RHEL 7 combines Gunicorn as a WSGI application server with Nginx acting as a reverse proxy to handle static files, SSL termination, and load balancing. This guide walks you through every step required to deploy a Flask application in this configuration on Red Hat Enterprise Linux 7, including creating a virtual environment, writing a systemd service unit, configuring Nginx, and managing environment variables securely.
Prerequisites
- A running RHEL 7 server with root or sudo access
- Python 3.6 or later installed (
yum install python3) - Nginx installed (
yum install nginx) - A non-root system user to run the application (e.g.,
flaskuser) - Firewall access on ports 80 and 443
- A domain name or server IP address for Nginx configuration
Step 1: Create a Dedicated System User
Running web applications as root is a security risk. Create a dedicated user that owns the application files and runs the Gunicorn process.
sudo useradd -r -s /sbin/nologin flaskuser
sudo mkdir -p /var/www/myflaskapp
sudo chown flaskuser:flaskuser /var/www/myflaskapp
The -r flag creates a system account and -s /sbin/nologin prevents interactive login, reducing the attack surface.
Step 2: Create a Python Virtual Environment
Virtual environments isolate your application’s Python dependencies from the system Python installation, preventing version conflicts.
sudo -u flaskuser python3 -m venv /var/www/myflaskapp/venv
Activate the virtual environment to install packages into it:
sudo -u flaskuser bash -c "source /var/www/myflaskapp/venv/bin/activate && pip install --upgrade pip"
Step 3: Install Flask and Gunicorn
With the virtual environment active, install Flask and Gunicorn using pip:
sudo -u flaskuser bash -c "
source /var/www/myflaskapp/venv/bin/activate
pip install flask gunicorn
"
Verify the installations:
sudo -u flaskuser /var/www/myflaskapp/venv/bin/pip show flask gunicorn
Step 4: Write the Flask Application
Create the application entry point at /var/www/myflaskapp/app.py. A minimal but realistic Flask application looks like this:
sudo -u flaskuser tee /var/www/myflaskapp/app.py <<'EOF'
from flask import Flask, jsonify
import os
app = Flask(__name__)
@app.route('/')
def index():
return jsonify({"status": "ok", "env": os.environ.get("APP_ENV", "production")})
@app.route('/health')
def health():
return jsonify({"healthy": True}), 200
if __name__ == '__main__':
app.run()
EOF
Test that Gunicorn can load the application directly before configuring systemd:
sudo -u flaskuser /var/www/myflaskapp/venv/bin/gunicorn
--workers 3
--bind 0.0.0.0:8000
app:app
--chdir /var/www/myflaskapp
If the application starts without errors, press Ctrl+C to stop it and proceed.
Step 5: Create an Environment File
Store sensitive configuration values—database URLs, secret keys, API tokens—in a dedicated environment file rather than hardcoding them in your application or service unit.
sudo tee /var/www/myflaskapp/.env <<'EOF'
APP_ENV=production
SECRET_KEY=replace-with-a-strong-random-value
DATABASE_URL=postgresql://user:password@localhost/mydb
EOF
sudo chown flaskuser:flaskuser /var/www/myflaskapp/.env
sudo chmod 640 /var/www/myflaskapp/.env
The 640 permission ensures only the flaskuser owner and its group can read the file; world access is denied.
Step 6: Create the Gunicorn systemd Service
Create a systemd unit file so that Gunicorn starts automatically on boot and restarts if it crashes. The EnvironmentFile directive loads your .env values into the process environment.
sudo tee /etc/systemd/system/gunicorn.service <<'EOF'
[Unit]
Description=Gunicorn WSGI server for Flask application
After=network.target
[Service]
User=flaskuser
Group=flaskuser
WorkingDirectory=/var/www/myflaskapp
EnvironmentFile=/var/www/myflaskapp/.env
ExecStart=/var/www/myflaskapp/venv/bin/gunicorn
--workers 3
--bind unix:/run/gunicorn/gunicorn.sock
--access-logfile /var/log/gunicorn/access.log
--error-logfile /var/log/gunicorn/error.log
app:app
ExecReload=/bin/kill -s HUP $MAINPID
RuntimeDirectory=gunicorn
LogsDirectory=gunicorn
Restart=on-failure
RestartSec=5s
KillMode=mixed
TimeoutStopSec=5
[Install]
WantedBy=multi-user.target
EOF
The RuntimeDirectory=gunicorn directive tells systemd to create /run/gunicorn/ automatically with correct permissions. Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicorn
Confirm the Unix socket exists:
ls -la /run/gunicorn/gunicorn.sock
Step 7: Configure Nginx as a Reverse Proxy
Nginx will accept all incoming HTTP requests and forward them to Gunicorn via the Unix socket. Create a server block configuration file:
sudo tee /etc/nginx/conf.d/myflaskapp.conf <<'EOF'
upstream gunicorn_backend {
server unix:/run/gunicorn/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
access_log /var/log/nginx/myflaskapp_access.log;
error_log /var/log/nginx/myflaskapp_error.log;
location / {
proxy_pass http://gunicorn_backend;
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_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
client_max_body_size 16m;
}
location /static/ {
alias /var/www/myflaskapp/static/;
expires 30d;
access_log off;
}
}
EOF
Test the Nginx configuration syntax and reload:
sudo nginx -t
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl reload nginx
Step 8: Configure the Firewall
Open ports 80 (HTTP) and 443 (HTTPS) in firewalld so external traffic can reach Nginx:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
If SELinux is enforcing, you must allow Nginx to connect to the Gunicorn socket:
sudo setsebool -P httpd_can_network_connect 1
Step 9: Verify the Deployment
Test the full stack end-to-end using curl:
curl -s http://yourdomain.com/
curl -s http://yourdomain.com/health
Check logs if there are any issues:
sudo journalctl -u gunicorn -n 50 --no-pager
sudo tail -n 50 /var/log/gunicorn/error.log
sudo tail -n 50 /var/log/nginx/myflaskapp_error.log
Step 10: Reloading the Application After Code Changes
After updating your Flask application code, reload Gunicorn gracefully without dropping active connections:
sudo systemctl reload gunicorn
For a full restart (e.g., after adding or removing workers in the service file):
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
You now have a production-ready Flask deployment on RHEL 7 with Gunicorn and Nginx. Gunicorn serves the Python application using multiple worker processes, while Nginx handles connection management, static file serving, and acts as the public-facing entry point. The systemd service ensures the application starts automatically on boot and recovers from failures, and the EnvironmentFile approach keeps sensitive credentials out of your codebase. As a next step, consider adding SSL certificates via Certbot and configuring log rotation with logrotate to manage disk usage over time.