Flask is a lightweight Python web framework well-suited for building APIs and web applications on RHEL 8. Gunicorn acts as the production-grade WSGI server that runs your Flask app, while Nginx sits in front as a reverse proxy handling incoming traffic. This guide walks through deploying a Flask application on RHEL 8 using a Python virtual environment, Gunicorn, and Nginx with a systemd service for process management. By the end you will have a production-ready deployment that survives reboots automatically.

Prerequisites

  • A RHEL 8 server with a non-root sudo user
  • Python 3.8 or higher (available via AppStream)
  • Nginx installed (dnf install -y nginx)
  • Firewall ports 80 and 443 open (firewall-cmd --permanent --add-service=http)
  • Basic familiarity with the command line

Step 1 — Install Python and Create a Virtual Environment

RHEL 8 ships Python 3 via AppStream. Install it along with the virtual environment tooling, then create an isolated environment for your project.

sudo dnf install -y python39 python39-pip python39-devel
mkdir -p ~/myflaskapp && cd ~/myflaskapp
python3.9 -m venv venv
source venv/bin/activate
pip install --upgrade pip

Step 2 — Install Flask and Gunicorn

With the virtual environment active, install Flask and Gunicorn using pip.

pip install flask gunicorn

Create a minimal Flask application in app.py:

cat > ~/myflaskapp/app.py << 'EOF'
from flask import Flask

app = Flask(__name__)
app.config['SECRET_KEY'] = 'change-this-to-a-random-secret'
app.config['DATABASE_URI'] = 'sqlite:///app.db'

@app.route('/')
def index():
    return '

Hello from Flask on RHEL 8

' @app.route('/health') def health(): return {'status': 'ok'}, 200 if __name__ == '__main__': app.run(debug=False) EOF

Step 3 — Run the Application with Gunicorn

Test that Gunicorn can serve the application before configuring it as a service. The -w 4 flag spawns four worker processes — a common starting point for multi-core servers.

cd ~/myflaskapp
source venv/bin/activate
gunicorn -w 4 -b 0.0.0.0:5000 app:app

Visit http://your-server-ip:5000 to verify the response, then press Ctrl+C to stop. Never use Flask’s built-in development server (app.run(debug=True)) in production — it is single-threaded and not hardened for public traffic.

Step 4 — Create a systemd Service for Gunicorn

A systemd unit file ensures Gunicorn restarts automatically on failure or reboot. Replace youruser with your actual username.

sudo tee /etc/systemd/system/gunicorn.service > /dev/null << 'EOF'
[Unit]
Description=Gunicorn daemon for Flask application
After=network.target

[Service]
User=youruser
Group=youruser
WorkingDirectory=/home/youruser/myflaskapp
ExecStart=/home/youruser/myflaskapp/venv/bin/gunicorn 
          --workers 4 
          --bind unix:/home/youruser/myflaskapp/gunicorn.sock 
          app:app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn
sudo systemctl status gunicorn

Using a Unix socket (gunicorn.sock) rather than a TCP port is preferred when Nginx runs on the same host — it avoids network overhead and is more secure.

Step 5 — Configure Nginx as a Reverse Proxy

Create an Nginx server block that forwards requests to the Gunicorn socket.

sudo tee /etc/nginx/conf.d/myflaskapp.conf > /dev/null << 'EOF'
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://unix:/home/youruser/myflaskapp/gunicorn.sock;
        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;
    }
}
EOF

sudo nginx -t
sudo systemctl enable --now nginx

Step 6 — Verify the Deployment

Check that both services are running and confirm the application responds through Nginx.

sudo systemctl status gunicorn nginx
curl -I http://localhost/
journalctl -u gunicorn -n 30

If you see a 502 Bad Gateway from Nginx, the socket file permissions are likely the issue. Ensure the Nginx worker process user (nginx) can read the socket directory by adding it to the application user’s group or by adjusting directory permissions.

Conclusion

You now have a Flask application running under Gunicorn on RHEL 8, managed by systemd, and served through Nginx as a reverse proxy. This stack is battle-tested, lightweight, and easy to scale by increasing the Gunicorn worker count or adding upstream servers in Nginx. For HTTPS, obtain a certificate with Certbot (dnf install -y certbot python3-certbot-nginx) and run certbot --nginx -d your-domain.com.

Next steps: Securing Flask with HTTPS and Certbot on RHEL 8, Using PostgreSQL with SQLAlchemy in Flask, and Containerizing a Flask App with Podman on RHEL 8.