Python powers some of the most widely deployed web applications, APIs, and data services in the world. Deploying a Flask or Django application on a Hong Kong VPS gives you direct CN2 GIA connectivity to mainland Chinese users, full control over your Python runtime and dependencies, and the flexibility to run any library, background worker, or database stack your application requires.
This guide covers the complete production deployment pipeline for Python web applications: virtualenv setup, Gunicorn WSGI server configuration, Nginx reverse proxy, and Let’s Encrypt SSL — the standard production stack for Python web apps on Linux.
Prerequisites
- A Hong Kong VPS running Ubuntu 22.04 LTS with at least 1 vCPU and 1 GB RAM
- A non-root sudo user (see our Node.js guide Step 1 for user creation)
- A domain name pointing to your VPS IP
- Your Flask or Django application code
Step 1: Install Python and Dependencies
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv python3-dev
sudo apt install -y build-essential libpq-dev nginx certbot python3-certbot-nginxVerify Python version:
python3 --versionUbuntu 22.04 ships with Python 3.10. For newer versions, use the deadsnakes PPA:
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt install -y python3.12 python3.12-venvStep 2: Deploy Your Application
mkdir -p /home/deploy/apps/myapp
cd /home/deploy/apps/myappClone your repository or transfer files:
git clone https://github.com/yourusername/your-repo.git .
Create and activate a virtual environment:
python3 -m venv venv
source venv/bin/activateInstall dependencies:
pip install -r requirements.txt
pip install gunicornCreate your environment variables file:
nano /home/deploy/apps/myapp/.envFLASK_ENV=production
SECRET_KEY=your_secret_key_here
DATABASE_URL=postgresql://user:password@localhost/dbname
DEBUG=Falsechmod 600 .envStep 3: Test with Gunicorn
Gunicorn is the standard WSGI server for Python web applications in production. Test that your app starts correctly:
For Flask:
gunicorn --bind 127.0.0.1:8000 app:appFor Django:
gunicorn --bind 127.0.0.1:8000 myproject.wsgi:applicationReplace app:app with module_name:flask_app_variable and myproject.wsgi with your Django project’s wsgi module path.
Test the response from a second SSH session:
curl http://127.0.0.1:8000If you receive a valid response, stop Gunicorn with Ctrl+C and proceed to configure it as a service.
Step 4: Create a Gunicorn Systemd Service
sudo nano /etc/systemd/system/myapp.service[Unit]
Description=Gunicorn daemon for myapp
After=network.target
[Service]
User=deploy
Group=www-data
WorkingDirectory=/home/deploy/apps/myapp
Environment="PATH=/home/deploy/apps/myapp/venv/bin"
EnvironmentFile=/home/deploy/apps/myapp/.env
# Flask:
ExecStart=/home/deploy/apps/myapp/venv/bin/gunicorn \
--workers 3 \
--bind unix:/run/myapp.sock \
--access-logfile /home/deploy/logs/access.log \
--error-logfile /home/deploy/logs/error.log \
app:app
# Django (uncomment and replace Flask line above):
# ExecStart=/home/deploy/apps/myapp/venv/bin/gunicorn \
# --workers 3 \
# --bind unix:/run/myapp.sock \
# myproject.wsgi:application
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetThe --workers 3 flag sets the number of Gunicorn worker processes. A common formula is (2 × CPU cores) + 1. For a 1 vCPU VPS, 3 workers is appropriate.
mkdir -p /home/deploy/logs
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myappStep 5: Configure Nginx as a Reverse Proxy
sudo nano /etc/nginx/sites-available/myappserver {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000" always;
# Static files served directly by Nginx
location /static/ {
alias /home/deploy/apps/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
location /media/ {
alias /home/deploy/apps/myapp/media/;
expires 7d;
}
# Proxy to Gunicorn via Unix socket
location / {
proxy_pass http://unix:/run/myapp.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_redirect off;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
client_max_body_size 10M;
gzip on;
gzip_types text/plain application/json application/javascript text/css;
}sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxStep 6: Obtain SSL Certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.comCertbot automatically configures Nginx with the certificate. Verify auto-renewal:
sudo certbot renew --dry-runStep 7: Django-Specific Configuration
For Django applications, complete these additional steps before starting Gunicorn:
source venv/bin/activate
cd /home/deploy/apps/myapp
# Collect static files
python manage.py collectstatic --noinput
# Run database migrations
python manage.py migrate
# Create superuser (first deployment only)
python manage.py createsuperuserIn your Django settings.py, ensure production settings are correct:
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
STATIC_ROOT = '/home/deploy/apps/myapp/static/'
MEDIA_ROOT = '/home/deploy/apps/myapp/media/'
# Security settings
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueStep 8: Deployment Update Script
nano /home/deploy/deploy.sh#!/bin/bash
set -e
APP_DIR="/home/deploy/apps/myapp"
echo "==> Pulling latest code..."
cd $APP_DIR
git pull origin main
echo "==> Installing dependencies..."
source venv/bin/activate
pip install -r requirements.txt
# Django only — comment out for Flask:
echo "==> Running migrations..."
python manage.py migrate --noinput
python manage.py collectstatic --noinput
echo "==> Restarting Gunicorn..."
sudo systemctl restart myapp
echo "==> Done."
sudo systemctl status myapp --no-pagerchmod +x /home/deploy/deploy.shConclusion
Your Python Flask or Django application is now running in production on a Hong Kong VPS with Gunicorn (multi-worker WSGI server), Nginx (reverse proxy with SSL), Let’s Encrypt (auto-renewing certificate), and CN2 GIA routing for optimal China-facing performance. This stack handles everything from lightweight Flask APIs to full-scale Django e-commerce platforms.
Looking for a Hong Kong VPS for Python deployments? Server.HK’s plans include KVM virtualisation, NVMe SSD, and CN2 GIA routing as standard.
Frequently Asked Questions
How many Gunicorn workers should I run on a 2 GB RAM Hong Kong VPS?
For a 2 vCPU / 2 GB RAM VPS, start with 3–5 Gunicorn workers using the (2 × CPU) + 1 formula. Monitor memory usage with htop — each Gunicorn worker consumes 50–150 MB RAM depending on your application’s memory footprint. Scale workers down if you approach 80% RAM usage.
Should I use Flask or Django for a new project on a Hong Kong VPS?
Flask is better for lightweight APIs, microservices, and projects where you want full control over component selection. Django is better for full-stack web applications with authentication, admin interfaces, ORM, and form handling built in. Both deploy identically via Gunicorn and Nginx — the choice is purely about your application’s needs.
Can I run Celery background workers alongside Gunicorn on the same VPS?
Yes. Create a separate systemd service for Celery workers, similar to the Gunicorn service in Step 4. Celery requires a message broker (Redis or RabbitMQ) — Redis is the most common choice and can be installed on the same VPS for low-traffic applications. Monitor combined RAM usage when running Gunicorn + Celery + Redis on a single instance.