How to Deploy a Flask App on VPS: Complete Production Guide
A step-by-step guide to deploying your Flask application on a Virtual Private Server (VPS) with Nginx, Gunicorn, and SSL certificates.
---
## Table of Contents
1. [Prerequisites](#prerequisites)
2. [Initial VPS Setup](#initial-vps-setup)
3. [Installing Required Software](#installing-required-software)
4. [Setting Up Your Flask Application](#setting-up-your-flask-application)
5. [Configuring Gunicorn](#configuring-gunicorn)
6. [Setting Up Nginx](#setting-up-nginx)
7. [SSL Certificate with Let's Encrypt](#ssl-certificate-with-lets-encrypt)
8. [Systemd Service Configuration](#systemd-service-configuration)
9. [Firewall Configuration](#firewall-configuration)
10. [Troubleshooting](#troubleshooting)
---
## Prerequisites
Before starting, ensure you have:
- A VPS running **Ubuntu 20.04/22.04** (DigitalOcean, Linode, Vultr, etc.)
- A domain name pointing to your VPS IP address
- SSH access to your server
- A Flask application ready to deploy
- Basic Linux command line knowledge
---
## Initial VPS Setup
### Step 1: Connect to Your VPS
```bash
ssh root@your_server_ip
```
### Step 2: Create a Non-Root User
```bash
# Create new user
adduser deploy
# Grant sudo privileges
usermod -aG sudo deploy
# Switch to new user
su - deploy
```
### Step 3: Set Up SSH Key Authentication (Recommended)
On your **local machine**:
```bash
# Generate SSH key (if you don't have one)
ssh-keygen -t ed25519 -C "[email protected]"
# Copy public key to server
ssh-copy-id deploy@your_server_ip
```
### Step 4: Update System Packages
```bash
sudo apt update && sudo apt upgrade -y
```
---
## Installing Required Software
### Install Python and pip
```bash
# Install Python 3 and pip
sudo apt install python3 python3-pip python3-venv -y
# Verify installation
python3 --version
pip3 --version
```
### Install Nginx
```bash
sudo apt install nginx -y
# Start and enable Nginx
sudo systemctl start nginx
sudo systemctl enable nginx
# Verify Nginx is running
sudo systemctl status nginx
```
### Install Additional Dependencies
```bash
# Install essential build tools
sudo apt install build-essential libssl-dev libffi-dev python3-dev -y
# Install Git (for cloning your repository)
sudo apt install git -y
```
---
## Setting Up Your Flask Application
### Step 1: Create Application Directory
```bash
# Create directory structure
sudo mkdir -p /var/www/myflaskapp
sudo chown -R deploy:deploy /var/www/myflaskapp
cd /var/www/myflaskapp
```
### Step 2: Clone Your Application
```bash
# Clone from Git repository
git clone https://github.com/yourusername/your-flask-app.git .
# Or create a simple test app
nano app.py
```
**Simple Flask Application (app.py):**
```python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return '<h1>Hello from Flask on VPS!</h1>'
@app.route('/health')
def health():
return {'status': 'healthy'}, 200
if __name__ == '__main__':
app.run()
```
### Step 3: Create Virtual Environment
```bash
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate
# Upgrade pip
pip install --upgrade pip
```
### Step 4: Install Dependencies
```bash
# Install Flask and Gunicorn
pip install flask gunicorn
# Or install from requirements.txt
pip install -r requirements.txt
# Add gunicorn to requirements
pip freeze > requirements.txt
```
### Step 5: Test Your Application
```bash
# Run Flask development server
python app.py
```
Visit `http://your_server_ip:5000` to verify it works (you may need to temporarily allow port 5000).
---
## Configuring Gunicorn
Gunicorn (Green Unicorn) is a production-ready WSGI HTTP server for Python.
### Step 1: Create WSGI Entry Point
```bash
nano /var/www/myflaskapp/wsgi.py
```
**wsgi.py:**
```python
from app import app
if __name__ == '__main__':
app.run()
```
### Step 2: Test Gunicorn
```bash
cd /var/www/myflaskapp
source venv/bin/activate
# Run Gunicorn
gunicorn --bind 0.0.0.0:8000 wsgi:app
```
Test by visiting `http://your_server_ip:8000`
### Step 3: Create Gunicorn Configuration File (Optional)
```bash
nano /var/www/myflaskapp/gunicorn.conf.py
```
**gunicorn.conf.py:**
```python
# Gunicorn configuration file
import multiprocessing
# Server socket
bind = 'unix:/var/www/myflaskapp/myflaskapp.sock'
backlog = 2048
# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
timeout = 120
keepalive = 5
# Process naming
proc_name = 'myflaskapp'
# Logging
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'info'
# Daemon mode
daemon = False
```
Create log directory:
```bash
sudo mkdir -p /var/log/gunicorn
sudo chown -R deploy:deploy /var/log/gunicorn
```
---
## Setting Up Nginx
Nginx acts as a reverse proxy, forwarding requests to Gunicorn.
### Step 1: Create Nginx Server Block
```bash
sudo nano /etc/nginx/sites-available/myflaskapp
```
**Nginx Configuration:**
```nginx
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Logging
access_log /var/log/nginx/myflaskapp_access.log;
error_log /var/log/nginx/myflaskapp_error.log;
# Max upload size
client_max_body_size 10M;
location / {
proxy_pass http://unix:/var/www/myflaskapp/myflaskapp.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;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
}
# Static files (if any)
location /static {
alias /var/www/myflaskapp/static;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Favicon
location /favicon.ico {
alias /var/www/myflaskapp/static/favicon.ico;
access_log off;
log_not_found off;
}
}
```
### Step 2: Enable the Site
```bash
# Create symbolic link
sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test Nginx configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
```
---
## SSL Certificate with Let's Encrypt
### Step 1: Install Certbot
```bash
sudo apt install certbot python3-certbot-nginx -y
```
### Step 2: Obtain SSL Certificate
```bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
```
Follow the prompts:
- Enter your email address
- Agree to the Terms of Service
- Choose whether to redirect HTTP to HTTPS (recommended: Yes)
### Step 3: Verify Auto-Renewal
```bash
# Test renewal process
sudo certbot renew --dry-run
# Check certbot timer
sudo systemctl status certbot.timer
```
Your Nginx configuration will be automatically updated for HTTPS.
---
## Systemd Service Configuration
Create a systemd service to manage Gunicorn.
### Step 1: Create Service File
```bash
sudo nano /etc/systemd/system/myflaskapp.service
```
**myflaskapp.service:**
```ini
[Unit]
Description=Gunicorn instance to serve myflaskapp
After=network.target
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/myflaskapp
Environment="PATH=/var/www/myflaskapp/venv/bin"
ExecStart=/var/www/myflaskapp/venv/bin/gunicorn --workers 3 --bind unix:myflaskapp.sock -m 007 wsgi:app
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
### Step 2: Start and Enable Service
```bash
# Reload systemd
sudo systemctl daemon-reload
# Start the service
sudo systemctl start myflaskapp
# Enable on boot
sudo systemctl enable myflaskapp
# Check status
sudo systemctl status myflaskapp
```
### Step 3: Useful Service Commands
```bash
# Restart application
sudo systemctl restart myflaskapp
# Stop application
sudo systemctl stop myflaskapp
# View logs
sudo journalctl -u myflaskapp -f
# View last 50 lines of logs
sudo journalctl -u myflaskapp -n 50
```
---
## Firewall Configuration
### Using UFW (Uncomplicated Firewall)
```bash
# Enable UFW
sudo ufw enable
# Allow SSH (important - do this first!)
sudo ufw allow OpenSSH
# Allow HTTP
sudo ufw allow 'Nginx HTTP'
# Allow HTTPS
sudo ufw allow 'Nginx HTTPS'
# Or allow both at once
sudo ufw allow 'Nginx Full'
# Check status
sudo ufw status
```
**Expected output:**
```
Status: active
To Action From
-- ------ ----
OpenSSH ALLOW Anywhere
Nginx Full ALLOW Anywhere
OpenSSH (v6) ALLOW Anywhere (v6)
Nginx Full (v6) ALLOW Anywhere (v6)
```
---
## Environment Variables
### Managing Secrets Securely
**Option 1: Using .env file**
```bash
nano /var/www/myflaskapp/.env
```
**.env:**
```
FLASK_ENV=production
SECRET_KEY=your-super-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost/dbname
```
Install python-dotenv:
```bash
pip install python-dotenv
```
Load in your app:
```python
from dotenv import load_dotenv
load_dotenv()
import os
SECRET_KEY = os.getenv('SECRET_KEY')
```
**Option 2: Using systemd environment file**
Update your service file:
```ini
[Service]
EnvironmentFile=/var/www/myflaskapp/.env
```
---
## Database Setup (Optional)
### PostgreSQL
```bash
# Install PostgreSQL
sudo apt install postgresql postgresql-contrib -y
# Create database and user
sudo -u postgres psql
# In PostgreSQL shell:
CREATE DATABASE myflaskapp;
CREATE USER flaskuser WITH PASSWORD 'securepassword';
GRANT ALL PRIVILEGES ON DATABASE myflaskapp TO flaskuser;
\q
```
### MySQL/MariaDB
```bash
# Install MariaDB
sudo apt install mariadb-server -y
# Secure installation
sudo mysql_secure_installation
# Create database
sudo mysql -u root -p
# In MySQL shell:
CREATE DATABASE myflaskapp;
CREATE USER 'flaskuser'@'localhost' IDENTIFIED BY 'securepassword';
GRANT ALL PRIVILEGES ON myflaskapp.* TO 'flaskuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
```
---
## Troubleshooting
### Common Issues and Solutions
#### 502 Bad Gateway
```bash
# Check if Gunicorn is running
sudo systemctl status myflaskapp
# Check socket file exists
ls -la /var/www/myflaskapp/myflaskapp.sock
# Check Gunicorn logs
sudo journalctl -u myflaskapp -n 50
# Check Nginx error logs
sudo tail -f /var/log/nginx/error.log
```
#### Permission Denied on Socket
```bash
# Fix socket permissions
sudo chown deploy:www-data /var/www/myflaskapp
chmod 755 /var/www/myflaskapp
# Restart services
sudo systemctl restart myflaskapp
sudo systemctl restart nginx
```
#### Application Not Starting
```bash
# Test manually
cd /var/www/myflaskapp
source venv/bin/activate
gunicorn --bind 0.0.0.0:8000 wsgi:app
# Check for Python errors
python wsgi.py
```
#### Static Files Not Loading
```bash
# Verify static directory exists
ls -la /var/www/myflaskapp/static
# Check Nginx configuration
sudo nginx -t
# Ensure correct permissions
sudo chown -R deploy:www-data /var/www/myflaskapp/static
```
### Useful Debugging Commands
```bash
# View real-time logs
sudo journalctl -u myflaskapp -f
# Check Nginx configuration
sudo nginx -t
# Check listening ports
sudo ss -tulpn | grep -E '(80|443|8000)'
# Check disk space
df -h
# Check memory usage
free -m
# Check running processes
htop
```
---
## Deployment Workflow
For future updates, use this workflow:
```bash
# SSH into server
ssh deploy@your_server_ip
# Navigate to app directory
cd /var/www/myflaskapp
# Activate virtual environment
source venv/bin/activate
# Pull latest changes
git pull origin main
# Install new dependencies (if any)
pip install -r requirements.txt
# Run database migrations (if using Flask-Migrate)
flask db upgrade
# Restart application
sudo systemctl restart myflaskapp
# Check status
sudo systemctl status myflaskapp
```
---
## Quick Reference
| Action | Command |
|--------|---------|
| Start app | `sudo systemctl start myflaskapp` |
| Stop app | `sudo systemctl stop myflaskapp` |
| Restart app | `sudo systemctl restart myflaskapp` |
| View logs | `sudo journalctl -u myflaskapp -f` |
| Restart Nginx | `sudo systemctl restart nginx` |
| Test Nginx config | `sudo nginx -t` |
| Renew SSL | `sudo certbot renew` |
---
## Conclusion
You now have a production-ready Flask application running on your VPS with:
- **Gunicorn** as the WSGI server
- **Nginx** as the reverse proxy
- **SSL/HTTPS** via Let's Encrypt
- **Systemd** for process management
- **UFW** firewall for security
Your application is now secure, performant, and ready to handle production traffic.
---
*Last Updated: January 2026*
*Tags: #Flask #Python #VPS #Deployment #Nginx #Gunicorn #DevOps #Ubuntu*
Comments (0)