Technology Encyclopedia Home >How to Deploy a Python Flask App on a Cloud Server — Gunicorn, Nginx, and HTTPS

How to Deploy a Python Flask App on a Cloud Server — Gunicorn, Nginx, and HTTPS

I built my first Flask app in maybe a weekend. Got it working locally, felt good about it. Then I went to actually put it online and realized I had no idea what I was doing.

flask run is for development. What runs it in production? What handles HTTPS? What happens when the server restarts — does the app start back up? I had to answer all of those questions one by one.

Turns out the answers aren't complicated, but nobody explains them together in one place. This guide is that: Gunicorn as the WSGI server, Nginx as the reverse proxy, systemd to keep it running, and Let's Encrypt for HTTPS. The complete picture.

I run this on Tencent Cloud Lighthouse with Ubuntu 22.04. Lighthouse suits Python app deployment well for a few reasons: instances spin up in under 2 minutes with a clean Ubuntu image (no conflicting pre-installed packages), OrcaTerm lets you manage the server from a browser without local SSH setup, and the snapshot feature means you can take a full server backup before a deployment in seconds. If your app grows, you can upgrade the instance spec (more RAM/CPU) from the control panel — no re-provisioning required.


Table of Contents

  1. The Production Flask Stack
  2. Prerequisites
  3. Part 1 — Provision the Server
  4. Part 2 — Install Python and Set Up a Virtual Environment
  5. Part 3 — Deploy Your Flask Application
  6. Part 4 — Install and Configure Gunicorn
  7. Part 5 — Create a systemd Service for Gunicorn
  8. Part 6 — Configure Nginx as a Reverse Proxy
  9. Part 7 — Enable HTTPS
  10. Part 8 — Environment Variables and Secrets
  11. The Gotcha: Gunicorn Can't Find the App
  12. Deployment Checklist

Key Takeaways

  • Never run Flask's development server in production — use Gunicorn
  • Use (2 × CPU cores) + 1 as the starting point for Gunicorn worker count
  • systemd keeps Gunicorn running after server reboots and crash restarts
  • Nginx handles HTTPS and static files; Gunicorn handles dynamic Python requests
  • Set environment variables in the systemd service file, not in the app code

The Production Flask Stack {#stack}

Internet → Nginx (port 80/443) → Gunicorn (Unix socket) → Flask app
Component Role
Nginx Terminates SSL, serves static files, proxies dynamic requests
Gunicorn WSGI server — runs multiple Flask worker processes
Flask Your application code
systemd Keeps Gunicorn running, restarts on crash

Flask's built-in development server (flask run) is not suitable for production — it's single-threaded and not designed for real traffic. Gunicorn handles concurrent requests properly.


Prerequisites {#prerequisites}

Requirement Notes
Cloud server Tencent Cloud Lighthouse Ubuntu 22.04
Non-root user with sudo See initial server setup guide
Domain name (optional) For HTTPS — not required for IP-only testing
A Flask application We'll create a minimal example if you don't have one

Part 1 — Provision the Server {#part-1}

ssh ubuntu@YOUR_SERVER_IP

Update packages:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv git nginx

Open required firewall ports:

sudo ufw allow 'Nginx Full'
sudo ufw allow ssh
sudo ufw enable

Part 2 — Install Python and Set Up a Virtual Environment {#part-2}

Ubuntu 22.04 ships with Python 3.10. Always use a virtual environment to isolate your app's dependencies:

# Create app directory
mkdir -p ~/apps/myflaskapp
cd ~/apps/myflaskapp

# Create a virtual environment
python3 -m venv venv

# Activate it
source venv/bin/activate

# Verify Python version
python --version

Install Flask and Gunicorn:

pip install flask gunicorn

Part 3 — Deploy Your Flask Application {#part-3}

Option A: Clone from GitHub

git clone https://github.com/your-username/your-flask-app.git .
source venv/bin/activate
pip install -r requirements.txt

Option B: Create a minimal example

If you're following along without an existing app:

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

app = Flask(__name__)

@app.route('/')
def index():
    return '<h1>Flask is running on Tencent Cloud Lighthouse.</h1>'

@app.route('/health')
def health():
    return {'status': 'ok'}

if __name__ == '__main__':
    app.run()
EOF

Test it locally first:

source venv/bin/activate
flask --app app run --host=0.0.0.0 --port=5000
# Visit http://YOUR_SERVER_IP:5000 to verify
# Press Ctrl+C to stop

Part 4 — Install and Configure Gunicorn {#part-4}

Gunicorn is your WSGI server. Test it manually first to make sure it can find and run your app:

cd ~/apps/myflaskapp
source venv/bin/activate

# Test Gunicorn with your app
# Format: gunicorn module:app_variable
gunicorn --bind 0.0.0.0:5000 app:app

Visit http://YOUR_SERVER_IP:5000 — you should see your Flask app.

Press Ctrl+C to stop.

Production Gunicorn configuration

For production, use more workers and a Unix socket instead of a TCP port:

gunicorn \
  --workers 3 \
  --bind unix:/run/myflaskapp.sock \
  --access-logfile /var/log/myflaskapp/access.log \
  --error-logfile /var/log/myflaskapp/error.log \
  app:app

Worker count rule of thumb: (2 × CPU cores) + 1. For a 2-core server, use 5 workers.


Part 5 — Create a systemd Service for Gunicorn {#part-5}

sudo mkdir -p /var/log/myflaskapp
sudo chown ubuntu:ubuntu /var/log/myflaskapp

sudo nano /etc/systemd/system/myflaskapp.service
[Unit]
Description=Gunicorn instance for myflaskapp
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/apps/myflaskapp
Environment="PATH=/home/ubuntu/apps/myflaskapp/venv/bin"
ExecStart=/home/ubuntu/apps/myflaskapp/venv/bin/gunicorn \
          --workers 3 \
          --bind unix:/run/myflaskapp.sock \
          --access-logfile /var/log/myflaskapp/access.log \
          --error-logfile /var/log/myflaskapp/error.log \
          app:app

# Restart on failure
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start myflaskapp
sudo systemctl enable myflaskapp

# Verify it's running
sudo systemctl status myflaskapp

# Check the socket was created
ls /run/myflaskapp.sock

Part 6 — Configure Nginx as a Reverse Proxy {#part-6}

sudo nano /etc/nginx/sites-available/myflaskapp
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    # Serve static files directly (skip Gunicorn)
    location /static/ {
        root /home/ubuntu/apps/myflaskapp;
        expires 30d;
    }

    # Proxy everything else to Gunicorn
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/myflaskapp.sock;
    }

    access_log /var/log/nginx/myflaskapp_access.log;
    error_log  /var/log/nginx/myflaskapp_error.log;
}
sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Visit http://yourdomain.com — your Flask app should be live.


Part 7 — Enable HTTPS {#part-7}

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

Choose option 2 (redirect HTTP to HTTPS) when prompted.

Test auto-renewal:

sudo certbot renew --dry-run

Part 8 — Environment Variables and Secrets {#part-8}

Never hardcode API keys, database passwords, or secret keys in your code. Use environment variables:

sudo nano /etc/systemd/system/myflaskapp.service

Add environment variables in the [Service] section:

[Service]
...
Environment="SECRET_KEY=your-secret-key-here"
Environment="DATABASE_URL=postgresql://user:password@localhost/mydb"
Environment="FLASK_ENV=production"

Or use an environment file (more secure):

sudo nano /etc/myflaskapp.env
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:password@localhost/mydb
FLASK_ENV=production
sudo chmod 600 /etc/myflaskapp.env

In the service file:

EnvironmentFile=/etc/myflaskapp.env

Reload and restart:

sudo systemctl daemon-reload
sudo systemctl restart myflaskapp

The Gotcha: Gunicorn Can't Find the App {#gotcha}

The most common error when first running Gunicorn:

Failed to find application: 'app:app'

This means either:

  1. The module name is wrong — check what your Python file is named (app.py → module app)
  2. The variable name is wrong — check what you named your Flask instance (app = Flask(__name__) → variable app)
  3. Gunicorn is running from the wrong directory — the WorkingDirectory in systemd must point to your app directory
  4. The virtual environment's Gunicorn is being used — always use the full path in ExecStart

Check the full path:

which gunicorn   # If venv is active, shows venv path
/home/ubuntu/apps/myflaskapp/venv/bin/gunicorn --version

Deployment Checklist {#checklist}

# After any code change, restart Gunicorn
sudo systemctl restart myflaskapp

# View application logs
sudo journalctl -u myflaskapp -f
tail -f /var/log/myflaskapp/error.log

# View Nginx logs
tail -f /var/log/nginx/myflaskapp_error.log

# Check service status
sudo systemctl status myflaskapp

Troubleshooting {#troubleshooting}

Issue Likely Cause Fix
Connection refused Service not running or wrong port Check systemctl status SERVICE and verify firewall rules
Permission denied Wrong file ownership or permissions Check file ownership with ls -la and use chown/chmod to fix
502 Bad Gateway Backend service not running Restart the backend service; check logs with journalctl -u SERVICE
SSL certificate error Certificate expired or domain mismatch Run sudo certbot renew and verify domain DNS points to server IP
Service not starting Config error or missing dependency Check logs with journalctl -u SERVICE -n 50 for specific error
Out of disk space Logs or data accumulation Run df -h to identify usage; clean logs or attach CBS storage
High memory usage Too many processes or memory leak Check with htop; consider upgrading instance plan if consistently high
Firewall blocking traffic Port not open in UFW or Lighthouse console Open port in Lighthouse console firewall AND sudo ufw allow PORT

Frequently Asked Questions {#faq}

What is Gunicorn and why is it needed for Flask?
Flask's built-in flask run server is single-threaded and not designed for production traffic. Gunicorn is a production WSGI server that runs multiple Flask worker processes, handles concurrent requests, and integrates with Nginx.

How many Gunicorn workers should I configure?
A common formula: (2 × CPU cores) + 1. For a 2-vCPU server, use 5 workers. More workers = more concurrent requests handled, but also more RAM usage.

How do I check if my Flask app is running on the server?
Check the systemd service status: sudo systemctl status gunicorn. View logs with sudo journalctl -u gunicorn -n 50. Test the socket directly with curl --unix-socket /run/gunicorn.sock http://localhost/.

How do I deploy updates to my Flask app?
Push your code to the server (via git or scp), restart Gunicorn: sudo systemctl restart gunicorn. With GitHub Actions, this can be automated on every push.

What is a WSGI application and how does it relate to Flask?
WSGI (Web Server Gateway Interface) is a Python standard for how web servers communicate with Python web applications. Flask is a WSGI application. Gunicorn is a WSGI server that runs Flask apps according to this standard.

Deploy your Flask app today:
👉 Tencent Cloud Lighthouse — Python-ready Ubuntu VPS
👉 View current pricing and promotions
👉 Explore all active deals and offers