How to Create and Deploy a Flask App on VPS — Production-Ready Guide (Free & Paid Options)

Keywords: Deploy Flask on VPS | Flask production setup | Flask with Nginx and Gunicorn | Ubuntu VPS Flask deployment

Learn How to Create and Deploy a Flask App on VPS: Step-by-Step Guide for Beginners to Intermediate:

So you've built a Flask app locally and it works perfectly on localhost:5000. Now what?

How to Create and Deploy a Flask App on VPS

Running Flask with python app.py is fine for development — but it is not safe, not scalable, and not suitable for real users. To go from your laptop to a live, accessible web application, you need a proper production stack. This guide walks you through exactly that — from zero to a fully deployed, HTTPS-secured Flask app on a real server.

What You Will Build

By the end of this tutorial, every request to your domain or server IP will flow through this production-grade architecture:

  Browser Request
      ↓
  Nginx (Port 80/443)       ← handles SSL, static files, load balancing
      ↓
  Unix Socket
      ↓
  Gunicorn (WSGI server)    ← runs your Python app with multiple workers
      ↓
  Flask Application         ← your app.py logic
      ↑
  systemd                   ← keeps Gunicorn alive, auto-restarts on crash

This is the same stack used in real-world Python web deployments — REST APIs, dashboards, microservices, and full web apps.

Why Not Just Use Shared Hosting or Heroku?

Great question. Here's why a VPS gives you more control and flexibility:

  • Shared hosting does not support Python WSGI apps, root SSH, or custom Nginx configuration.
  • Heroku free tier was discontinued; paid plans start at $7/month with container limits.
  • A VPS gives you root access, full Nginx control, persistent file storage, and the ability to run any service — at the same or lower cost.

If you're serious about Python web development, understanding VPS deployment is a must-have skill for your resume and real projects.

Table of Contents

  1. Server Setup
  2. Create a Basic Flask App Locally
  3. Connect to VPS via SSH
  4. Install Required Dependencies on VPS
  5. Upload Flask Project to VPS
  6. Configure Gunicorn
  7. Configure Nginx as Reverse Proxy
  8. Configure Firewall (UFW)
  9. Domain & SSL with Certbot
  10. 10. Testing & Debugging
  11. Conclusion

Prerequisites

Before starting, make sure you have:

  • Basic Python knowledge — you've written a Flask app before.
  • A terminal / command line (Linux, macOS, or WSL on Windows).
  • An Ubuntu 20.04 or 22.04 server — free (AWS EC2 t2.micro) or paid VPS.
  • SSH access to your server.
  • A domain name (optional — the tutorial works with just a server IP)

💡 New to Linux? Review the Linux File Navigation Tutorial and Linux Server Setup Guide on DevspireHub before starting this guide.

Introduction

Flask is a lightweight Python web framework built for speed and flexibility. Unlike Django, Flask gives you only what you need — a router, a request context, and a templating engine — then gets out of the way. This makes it the go-to framework for REST APIs, microservices, dashboards, and small-to-medium web apps.

VPS vs Shared Hosting vs Cloud Platforms

Factor Shared Hosting VPS (Paid) AWS EC2 Free Tier
Python WSGI support ❌ Limited ✅ Full ✅ Full
Root SSH access ❌ No ✅ Yes ✅ Yes
Nginx + Gunicorn config ❌ No ✅ Yes ✅ Yes
Monthly cost ₹300–600 ₹400–1,500 ₹0 (12 months)
SSL (HTTPS) Paid add-on Free via Certbot Free via Certbot
Best for Static/PHP sites Production Flask apps Learning + MVP

Cost Overview — Free vs Paid

Component Free Option Paid Option
Server (VPS) AWS EC2 Free Tier (12 months) Hostinger KVM1
Domain name Use server IP directly ~₹600–900/year from HostingRaja / Namecheap
SSL Certificate Let's Encrypt via Certbot — always free
Flask, Nginx, Gunicorn Always free (open source)
Ubuntu OS Always free

✅ Recommended starting path: Use AWS Free Tier (free server) + your existing devspirehub.com domain or just the server IP. Every tutorial step works identically on AWS EC2 and a paid VPS.

1. Server Setup — Free vs Paid (Choose One)

Option A: Free — AWS EC2 Free Tier

AWS Free Tier includes:
  ✅ EC2 t2.micro (1 vCPU, 1 GB RAM)
  ✅ 750 hours/month — enough for 1 always-on server
  ✅ 30 GB EBS SSD storage
  ✅ Free for 12 months after account creation
  ✅ Ubuntu 22.04 AMI available

Setup steps:

  1. Go to aws.amazon.com ⟶ Create free account
  2. Console → EC2 → Launch Instance
  3. Name: flaskapp-server
  4. AMI: Ubuntu Server 22.04 LTS (Free Tier eligible)
  5. Instance type: t2.micro (Free Tier eligible)
  6. Key pair: Create new → download .pem file → save safely
  7. Security Group — allow these ports:
    • SSH (22) — Your IP
    • HTTP (80) — Anywhere
    • HTTPS (443) — Anywhere
  8. Storage: 20–30 GB gp2 (Free Tier covers 30 GB)
  9. Click Launch Instance

Get your public IP from EC2 dashboard. SSH command:

# AWS EC2 uses ubuntu as the default user (not root)
ssh -i ~/.ssh/your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP

# Fix key permissions if SSH complains
chmod 400 ~/.ssh/your-key.pem

⚠️ AWS Free Tier Note: Free for 12 months. After that, t2.micro costs ~₹650/month. Set a billing alert at $1 in AWS Console → Billing → Budgets to avoid surprise charges.

Option B: Paid — VPS Provider

Provider Plan RAM Price Best For
Hostinger KVM 1 4 GB ~₹450/month Best value, beginner friendly
Hetzner CX11 2 GB ~₹380/month Cheapest powerful option
DigitalOcean Basic 1 GB ~₹850/month Great docs + community
Linode Nanode 1 GB ~₹850/month Stable and reliable

Setup steps (same for all):

  1. Sign up → Create a new server/droplet/VPS.
  2. Select Ubuntu 22.04 LTS.
  3. Choose your plan.
  4. Add your SSH public key during setup (or use password initially).
  5. Get server IP from dashboard.
# Connect as root (Hostinger/ DigitalOcean / Hetzner)
ssh root@YOUR_SERVER_IP

# Or with SSH key
ssh -i ~/.ssh/your-key root@YOUR_SERVER_IP

2. Create a Basic Flask App Locally

Build and test locally before touching the server.

2.1 Install Python and create a virtual environment

# Check Python version (need 3.8+)
python3 --version

# Install virtualenv
pip3 install virtualenv

# Create project directory
mkdir ~/flaskapp && cd ~/flaskapp

# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate # Linux / macOS
# venv\Scripts\activate # Windows

The purpose of those commands is to create an isolated Python environment for that specific Flask project, so its packages don’t interfere with system Python or other projects.

  • pip3 install virtualenv
  • Installs the tool that lets you create isolated Python environments (not strictly needed if you use python3 -m venv, but often shown in older guides).

  • mkdir ~/flaskapp && cd ~/flaskapp
  • Creates a dedicated project folder and moves into it.

  • python3 -m venv venv
  • Creates a virtual environment named venv inside the project directory. This is a self‑contained Python + site‑packages folder just for this project.

  • source venv/bin/activate (Linux/macOS) / venv\Scripts\activate (Windows)
    • python and pip now point to the project’s interpreter.
    • Any pip install flask affects only this project, not the whole system.

In short: you use these commands so each project has its own clean, reproducible dependency set, avoiding version conflicts and “it broke my global Python” issues.

2.2 Install Flask and Gunicorn

pip install flask gunicorn python-dotenv
pip freeze > requirements.txt

requirements.txt records all dependencies so the server installs exact same versions.

Those three packages each solve a different part of running a real Flask app:

  • flask
  • Installs the web framework itself.

    You use it to define routes, handle requests, render templates, etc. Without this, there is no Flask app.

  • gunicorn
  • Installs a production WSGI server.

    • You use flask run only for local development.
    • In production (on a VPS), you run something like gunicorn app:app behind Nginx or a reverse proxy so your app can handle real traffic efficiently and safely.
  • python-dotenv
  • Installs support for loading environment variables from a .env file.

    • Lets you keep secrets and config (e.g., SECRET_KEY, DB URLs) in .env instead of hard‑coding them.
    • On startup, it reads .env and sets those values in os.environ so Flask can use them.

Together:

flask = framework,

gunicorn = production server,

python-dotenv = configuration/secrets management via environment variables.

2.3 Create app.py — full working example

# app.py
from flask import Flask, jsonify, request
import os
from datetime import datetime

app = Flask(__name__)

# Load secret key from environment — never hardcode this
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-fallback-key')

@app.route('/')
def index():
    return jsonify({
        "status":  "ok",
        "message": "Flask app is live!",
        "time":    datetime.utcnow().isoformat()
    })

@app.route('/health')
def health():
    return jsonify({"status": "healthy"}), 200

@app.route('/api/greet', methods=['POST'])
def greet():
    data = request.get_json()
    name = data.get('name', 'World') if data else 'World'
    return jsonify({"greeting": f"Hello, {name}!"})

# Only used during local development
# Gunicorn replaces this entirely in production
if __name__ == '__main__':
    debug_mode = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
    app.run(host='0.0.0.0', port=5000, debug=debug_mode)

Explanation:

Imports

from flask import Flask, jsonify, request
import os
from datetime import datetime
  • Flask — the core app class.
  • jsonify — converts Python dicts into proper JSON HTTP responses.
  • request — lets you read incoming request data (JSON body, headers, etc.).
  • os — used to read environment variables securely.
  • datetime — used to show the current server time.

App Initialization

app = Flask(__name__)

Creates the Flask application instance. __name__ tells Flask where to look for templates and static files.

Secret Key Configuration

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-fallback-key')
  • Reads SECRET_KEY from the environment (set in .env via python-dotenv).
  • If not found, falls back to code.'dev-fallback-key' only for local dev.
  • Used internally by Flask for session signing and security features.
  • Never hardcode a real secret key — if pushed to GitHub, it becomes a security vulnerability.

Route 1 — Homepage (/)

@app.route('/')
def index():
    return jsonify({
        "status":  "ok",
        "message": "Flask app is live!",
        "time":    datetime.utcnow().isoformat()
    })
  • Responds to GET / with a JSON object.
  • Returns app status + current UTC timestamp.
  • Useful as a quick sanity check that the app is running.

Route 2 — Health Check (/health)

@app.route('/health')
def health():
    return jsonify({"status": "healthy"}), 200
  • A dedicated endpoint that always returns 200 OK when the app is alive.
  • Used by monitoring tools (UptimeRobot, Docker healthcheck, Nginx, Kubernetes) to verify the app is up.
  • The , 200 explicitly sets the HTTP status code

Route 3 — API Endpoint (/api/greet)

@app.route('/api/greet', methods=['POST'])
def greet():
    data = request.get_json()
    name = data.get('name', 'World') if data else 'World'
    return jsonify({"greeting": f"Hello, {name}!"})
  • Only accepts POST requests.
  • Reads the JSON body from the incoming request.
  • Extracts the name field — defaults to "World" if:
    • No JSON body was sent, or
    • The name key is missing
  • Returns a personalized greeting.

Example request:

POST /api/greet
{ "name": "Arjun" }

Response:

{ "greeting": "Hello, Arjun!" }

Production vs Development Entry Point

if __name__ == '__main__':
    debug_mode = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
    app.run(host='0.0.0.0', port=5000, debug=debug_mode)
  • if __name__ == '__main__' — this block only runs when you execute python app.py directly.
  • It is completely ignored when Gunicorn runs the app in production (gunicorn app:app).
  • host='0.0.0.0' — makes the app accessible from any network interface (not just localhost).
  • debug_mode — reads FLASK_DEBUG from .env; debug mode is never True in production because it exposes sensitive error details.

Summary — What Each Part Does

Section Purpose
Imports Load Flask tools + standard libraries
app = Flask(...) Create the application instance
SECRET_KEY Secure config loaded from environment
GET / Status check with timestamp
GET /health Monitoring/uptime probe endpoint
POST /api/greet Demonstrates JSON request handling
if __name__... Local dev runner only — Gunicorn ignores this in production

2.4 Create the WSGI entry point

# wsgi.py
from app import app

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

2.5 Create .env for local secrets

# .env (NEVER commit this to Git — add to .gitignore)
SECRET_KEY=your-very-long-random-secret-key-here
FLASK_DEBUG=true

2.6 Create .gitignore

# .gitignore
venv/
.env
__pycache__/
*.pyc
*.pyo
instance/
.DS_Store

2.7 Run locally to verify

# Flask dev server
python app.py

# OR test Gunicorn locally
gunicorn --bind 0.0.0.0:5000 wsgi:app

# Test in another terminal
curl http://localhost:5000/
curl http://localhost:5000/health

You should see {"status": "ok", ...} — app is working. Deactivate:

deactivate

2.8 Final local directory structure

flaskapp/
├── app.py             ← main application
├── wsgi.py            ← Gunicorn entry point
├── requirements.txt   ← pip dependencies
├── .env               ← local secrets (NEVER upload)
├── .gitignore
└── venv/              ← virtual environment (NEVER upload)

3. Connect to VPS via SSH

# AWS EC2 (Free Tier)
ssh -i ~/.ssh/your-key.pem ubuntu@YOUR_EC2_PUBLIC_IP

# Paid VPS (Hostinger/ DigitalOcean / Hetzner)
ssh root@YOUR_SERVER_IP

Create a non-root deployment user

Running as root is a security risk. Create a dedicated user:

# Create user
adduser flaskuser

# Grant sudo access
usermod -aG sudo flaskuser

# Copy SSH keys so you can log in as this user
rsync --archive --chown=flaskuser:flaskuser ~/.ssh /home/flaskuser

# Switch to new user
su - flaskuser

AWS EC2 note: The default user is already ubuntu (non-root with sudo). You can skip user creation and use ubuntu directly, or create flaskuser and use that.

Update server packages

sudo apt update && sudo apt upgrade -y

# Reboot if kernel was updated
sudo reboot

After reboot, SSH back in as flaskuser (or ubuntu on AWS).

4. Install Required Dependencies on VPS

# Python 3, pip, and build tools
sudo apt install python3 python3-pip python3-dev build-essential -y

# Verify versions
python3 --version
pip3 --version

# virtualenv
python3 -m venv myenv
source myenv/bin/activate
sudo pip3 install virtualenv

# Nginx web server
sudo apt install nginx -y

# Verify Nginx is running
sudo systemctl status nginx
# Expected: active (running)

Gunicorn will be installed inside the project virtual environment in Step 5.

Python 3, pip & Build Tools

sudo apt install python3 python3-pip python3-dev build-essential -y

This installs 4 packages in one shot:

Package What it does
python3 Python 3 interpreter — runs .py files
python3-pip Package manager for Python — installs libraries like Flask, requests
python3-dev Header files needed to compile Python C extensions (e.g. psycopg2, Pillow)
build-essential C/C++ compilers (gcc, make) — required to build packages from source

The -y flag auto-confirms the installation without prompting.

python3 --version    # Prints: Python 3.x.x
pip3 --version       # Prints: pip 23.x.x

These verify the installation was successful and show which versions are active.

Virtual Environment

python3 -m venv myenv

Creates an isolated Python environment in a folder called myenv/. Think of it as a separate box where your project's packages live — completely independent from system Python. This prevents version conflicts between projects.

source myenv/bin/activate

Activates the virtual environment. After this:

  • python points to myenv/bin/python (not system Python).
  • pip install installs only inside myenv/ (not globally).
  • Your terminal prompt changes to (myenv) user@host:~$.
sudo pip3 install virtualenv

Installs the virtualenv tool globally. Note: python3 -m venv (built-in) already does the job — virtualenv is an older, third-party alternative with extra features. You usually don't need both — python3 -m venv is sufficient for most projects.

⚠️ Best practice: Avoid sudo pip3 install — it installs globally and can break system packages. Use a venv instead.

Nginx Web Server

sudo apt install nginx -y

Installs Nginx — a high-performance web server used in production as a reverse proxy in front of Gunicorn. It handles:

  • Incoming HTTP/HTTPS traffic from browsers.
  • SSL/TLS termination.
  • Static file serving (much faster than Python).
  • Forwarding dynamic requests to Gunicorn on 127.0.0.1:5000.
sudo systemctl status nginx

Checks if Nginx is running. You expect to see:

Active: active (running)

This confirms Nginx started successfully after install. Ubuntu 22.04+ auto-starts and auto-enables Nginx on install.

How They All Work Together

Browser ⟶ Nginx (port 80/443) ⟶ Gunicorn (port 5000) ⟶ Flask App ⟶ Response
  • Python 3 runs your Flask code.
  • venv isolates your dependencies cleanly.
  • Gunicorn serves Flask as a production WSGI server.
  • Nginx sits in front, handles SSL + static files + load balancing.

5. Upload Flask Project to VPS

Method A: SCP (quick one-time upload)

# Run on your LOCAL machine

# AWS EC2
scp -i ~/.ssh/your-key.pem -r ~/flaskapp ubuntu@YOUR_EC2_IP:/home/ubuntu/

# Paid VPS
scp -r ~/flaskapp flaskuser@YOUR_SERVER_IP:/home/flaskuser/

Method B: Git (recommended for ongoing projects)

# On LOCAL machine — push to GitHub
cd ~/flaskapp
git init
git add .
git commit -m "Initial Flask app"
git remote add origin https://github.com/yourusername/flaskapp.git
git push -u origin main

Fix:

1. Create Token (if you haven't already)

Go to: https://github.com/settings/tokens.

Generate new token (classic) → repo permission → Generate token → copy it.

2. Push Using the Token

git push -u origin main

When prompted:

Username for 'https://github.com': yourusername
Password for 'https://yourusername@github.com': ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

3. Verify It Worked

git log --oneline -3 # Should show your commits
# On SERVER — clone the repo
cd /home/flaskuser # or /home/ubuntu for AWS

git clone https://github.com/yourusername/flaskapp.git
cd flaskapp

Set up virtual environment and install dependencies on server

cd /home/flaskuser/flaskapp # or /home/ubuntu/flaskapp on AWS

python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
deactivate

Create .env file on server (production secrets)

nano /home/flaskuser/flaskapp/.env

Add:

SECRET_KEY=generate-a-long-random-string-here
FLASK_DEBUG=false
FLASK_ENV=production
# DATABASE_URL=postgresql://user:pass@localhost/mydb

Generate a strong secret key:

openssl rand -hex 32
# Copy output as your SECRET_KEY value

Final VPS directory structure

/home/flaskuser/flaskapp/       ← /home/ubuntu/flaskapp on AWS
├── app.py
├── wsgi.py
├── requirements.txt
├── .env                        ← created directly on server
└── venv/                       ← created on server

6. Configure Gunicorn

6.1 Test Gunicorn manually

cd /home/flaskuser/flaskapp
source venv/bin/activate

gunicorn --bind 0.0.0.0:5000 wsgi:app
# Expected: [INFO] Listening at: http://0.0.0.0:5000

# Test from another terminal or browser
curl http://YOUR_SERVER_IP:5000/

deactivate

6.2 Create the systemd service file

sudo nano /etc/systemd/system/flaskapp.service
[Unit]
Description=Gunicorn instance to serve Flask application
After=network.target

[Service]
# --- CHANGE THIS based on your setup ---
# AWS EC2:   User=ubuntu  and paths use /home/ubuntu/flaskapp
# Paid VPS:  User=flaskuser and paths use /home/flaskuser/flaskapp
User=flaskuser
Group=www-data

WorkingDirectory=/home/flaskuser/flaskapp
EnvironmentFile=/home/flaskuser/flaskapp/.env
Environment="PATH=/home/flaskuser/flaskapp/venv/bin"

# Workers formula: (2 × CPU cores) + 1
# t2.micro (1 CPU) → 3 workers
# Paid VPS 2 CPU   → 5 workers
ExecStart=/home/flaskuser/flaskapp/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/home/flaskuser/flaskapp/flaskapp.sock \
    -m 007 \
    --access-logfile /var/log/flaskapp/access.log \
    --error-logfile  /var/log/flaskapp/error.log \
    wsgi:app

Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

AWS EC2 users: Replace all flaskuser with ubuntu and /home/flaskuser with /home/ubuntu in the service file.

6.3 Create log directory and enable service

# Create log directory
sudo mkdir -p /var/log/flaskapp
sudo chown flaskuser:flaskuser /var/log/flaskapp
# AWS: sudo chown ubuntu:ubuntu /var/log/flaskapp

# Reload systemd, enable, and start
sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp

# Verify
sudo systemctl status flaskapp
# Expected: Active: active (running)

A flaskapp.sock Unix socket file will appear in your project directory — Nginx will proxy through this.

7. Configure Nginx as Reverse Proxy

7.1 Create the Nginx server block

sudo nano /etc/nginx/sites-available/flaskapp
server {
    listen 80;

    # --- CHOOSE BASED ON YOUR SETUP ---
    # Free (no domain): use server IP
    # server_name YOUR_SERVER_IP;
    #
    # Free (AWS + no domain): use EC2 public DNS
    # server_name ec2-xx-xx-xx-xx.compute.amazonaws.com;
    #
    # Paid domain: use your domain
    # server_name yourdomain.com www.yourdomain.com;
    server_name YOUR_SERVER_IP_OR_DOMAIN;

    access_log /var/log/nginx/flaskapp_access.log;
    error_log  /var/log/nginx/flaskapp_error.log;

    client_max_body_size 16M;

    # Static files served by Nginx directly (faster)
    location /static {
        alias /home/flaskuser/flaskapp/static;
        # AWS: alias /home/ubuntu/flaskapp/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # All other requests → Gunicorn via Unix socket
    location / {
        include proxy_params;
        proxy_pass http://unix:/home/flaskuser/flaskapp/flaskapp.sock;
        # AWS: proxy_pass http://unix:/home/ubuntu/flaskapp/flaskapp.sock;

        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 60s;
        proxy_read_timeout    60s;
        proxy_send_timeout    60s;
    }
}

7.2 Enable site and restart Nginx

# Enable site
sudo ln -s /etc/nginx/sites-available/flaskapp \
/etc/nginx/sites-enabled/

# Remove default site
sudo rm -f /etc/nginx/sites-enabled/default

# Test config — always do this before restarting
sudo nginx -t
# Expected: syntax is ok / test is successful

# Reload Nginx
sudo systemctl reload nginx

Visit http://YOUR_SERVER_IP — you should see your Flask JSON response.

8. Configure Firewall (UFW)

# Allow SSH first — do this BEFORE enabling UFW
sudo ufw allow ssh
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 'Nginx Full'

# Enable firewall
sudo ufw enable

# Verify active rules
sudo ufw status verbose

AWS EC2 users: AWS has its own Security Groups firewall that you configured during instance creation. UFW is optional on EC2 but adds a second layer of defence — still recommended.

9. Domain & SSL with Certbot

Domain Options — Free vs Paid

Option A: Free — Use Server IP (no domain, no SSL)

No additional setup needed. Your app is accessible at:

http://YOUR_SERVER_IP

This is fine for learning and internal tools. You cannot get SSL without a domain name — Certbot requires DNS. Skip the rest of Step 8 if using IP only.

Option B: Free Domain Options

Several registrars offer free domains:

Provider Free Domain Notes
Freenom .tk, .ml, .ga Unreliable, not recommended for production
GitHub Student Pack .me (1 year free) Requires GitHub Student account
AWS Route 53 No free domains, ~$12/year for .com

💡 Free domains from Freenom are often unreliable and get revoked. For any real project, a paid domain is strongly recommended.

Option C: Paid Domain (Recommended for Production)

Provider Type Price / Note India-friendly
HostingRaja Paid ~₹800/year ✅ (local support)
Namecheap Paid ~₹900/year
GoDaddy Paid ~₹999/year
Google Domains Paid ~₹1,000/year
GitHub Student Free .me (1 year) Requires Student ID
Freenom Free .tk, .ml, .ga ❌ Unreliable

9.1 Point your domain to the server (if using a domain)

Type: A Name: @ Value: YOUR_SERVER_IP TTL: 300
Type: A Name: www Value: YOUR_SERVER_IP TTL: 300

Wait 5–15 minutes, then verify:

ping yourdomain.com
# Should resolve to YOUR_SERVER_IP

Update your Nginx config to use the domain:

sudo nano /etc/nginx/sites-available/flaskapp
# Change: server_name yourdomain.com www.yourdomain.com;
sudo nginx -t && sudo systemctl reload nginx

9.2 Install Certbot and generate SSL (Free — Let's Encrypt)

SSL via Certbot is always free regardless of whether you use a free or paid VPS:

# Install Certbot + Nginx plugin
sudo apt install certbot python3-certbot-nginx -y

# Generate SSL certificate (replace with your domain)
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Certbot prompts:
# 1. Enter email address
# 2. Agree to terms (A)
# 3. Choose redirect: select 2 (HTTP → HTTPS auto-redirect)

Certbot automatically updates your Nginx config to add listen 443 ssl and certificate paths.

9.3 Verify auto-renewa

# Dry-run to confirm renewal works
sudo certbot renew --dry-run

# Check renewal timer
sudo systemctl status certbot.timer

Let's Encrypt certificates expire every 90 days — the timer renews them automatically.

Visit https://yourdomain.com — padlock in browser confirms SSL is working.

10. Testing & Debugging

Check service logs

# Gunicorn service status
sudo systemctl status flaskapp

# Live log stream
sudo journalctl -u flaskapp -f

# Application logs
tail -f /var/log/flaskapp/access.log
tail -f /var/log/flaskapp/error.log

# Nginx logs
tail -f /var/log/nginx/flaskapp_access.log
tail -f /var/log/nginx/flaskapp_error.log

Common errors and fixes

Error Cause Fix
502 Bad Gateway Nginx can't reach Gunicorn socket Verify socket path matches in Nginx and service file; sudo systemctl status flaskapp
Permission denied on .sock Nginx user can't read socket sudo usermod -aG flaskuser www-data && sudo systemctl restart nginx
ModuleNotFoundError Missing Python package source venv/bin/activate && pip install -r requirements.txt
App not updating Old Gunicorn workers running sudo systemctl restart flaskapp
Certbot fails Domain not resolving to server Wait for DNS propagation; verify with dig yourdomain.com
AWS port 80 not responding Security Group not open EC2 Console → Security Groups → add HTTP inbound rule
nginx -t syntax error Config typo Read error line and fix; check semicolons and braces

Fix socket permission issues

sudo usermod -aG flaskuser www-data
# AWS: sudo usermod -aG ubuntu www-data

sudo systemctl restart nginx
sudo systemctl restart flaskapp

End-to-end test

# No domain (IP only)
curl http://YOUR_SERVER_IP/
curl http://YOUR_SERVER_IP/health

# With domain + SSL
curl https://yourdomain.com/
curl https://yourdomain.com/health

curl -X POST https://yourdomain.com/api/greet \
-H "Content-Type: application/json" \
-d '{"name": "Wave Function"}'

Production Best Practices

Never hardcode secrets

# app.py — always read from environment
import os

app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['DATABASE_URL'] = os.environ.get('DATABASE_URL')

if not app.config['SECRET_KEY']:
raise RuntimeError("SECRET_KEY is not set!")

Always disable debug mode in production

# /home/flaskuser/flaskapp/.env
FLASK_DEBUG=false
FLASK_ENV=production

Debug mode exposes an interactive Python debugger over HTTP — a critical security hole in production.

File permissions

# App files
sudo chown -R flaskuser:www-data /home/flaskuser/flaskapp
# AWS: sudo chown -R ubuntu:www-data /home/ubuntu/flaskapp

sudo chmod -R 750 /home/flaskuser/flaskapp

# .env — owner-read only
chmod 600 /home/flaskuser/flaskapp/.env

# Log directory
sudo chmod 755 /var/log/flaskapp

Basic server hardening

# 1. Disable root SSH login
sudo nano /etc/ssh/sshd_config
# Set: PermitRootLogin no
# Set: PasswordAuthentication no
sudo systemctl restart sshd

# 2. Install fail2ban (blocks SSH brute force)
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban

# 3. Automatic security updates
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure unattended-upgrades

# 4. Check listening ports (should only see 22, 80, 443)
sudo ss -tlnp

Update app without downtime

cd /home/flaskuser/flaskapp
git pull origin main

source venv/bin/activate
pip install -r requirements.txt
deactivate

# Graceful reload — zero downtime
sudo systemctl reload flaskapp

Full Cost Summary

Setup Server Domain SSL Total/month
Full free (learning) AWS Free Tier ₹0 Use server IP N/A ₹0
Free server + paid domain AWS Free Tier ₹0 ₹75/month (₹900/yr) ₹0 Certbot ~₹75
Paid VPS + no domain Hostinger₹450 Use server IP N/A ₹450
Full production Hostinger₹450 ₹75/month ₹0 Certbot ~₹525

Conclusion

You now have a fully production-ready Flask deployment with both free and paid paths clearly mapped out:

Internet → Nginx (80/443) → Unix Socket → Gunicorn → Flask App
                                  ↑
                           systemd manages
                           (auto-start / auto-restart)

Recommended path based on your situation:

  • Start now for free → AWS EC2 Free Tier + server IP, no domain needed.
  • Add a domain → point your existing devspirehub.com subdomain (e.g., app.devspirehub.com) — no extra cost since you already own it.
  • Go fully production → upgrade to a paid VPS (HostingerKVM1 ~₹450/month) when your app has real traffic.

What You Can Add Next?

  1. Add PostgreSQL database — sudo apt install postgresql -y + SQLAlchemy in Flask.
  2. CI/CD with GitHub Actions — auto-deploy on every git push to main.
  3. Monitoring — UptimeRobot (free) for uptime alerts.
  4. Containerise with Docker — wrap Flask + Gunicorn into a portable Docker image.
  5. Scale horizontally — add a second VPS + Nginx load balancer when traffic grows.

Other Projects

Space Shooter Game Python Pygame Tutorial

Shooter Game

This is a beginner-friendly guide for building a Space Shooter game with Python and Pygame, covering coding concepts and project structure.

Python Pygame
View Project
ATM Management System Python Tutorial

ATM Management System

This Python application implements a multi-user ATM system with SQLite-backed persistence, featuring account management, financial transactions, and administrative controls.

Python SQLite
View Project
Weather App HTML CSS JavaScript Tutorial

Weather App

Responsive weather app with real-time API data, feature comparison, and intuitive design for global city forecasts.

HTML CSS JavaScript
View Project
Team Card App HTML CSS JavaScript Tutorial

Team Card App

Interactive team card application for cricket, featuring dynamic team selection, player filters, and customizable light/dark themes.

HTML CSS JavaScript
View Project
Password Strength Checker C++ Tutorial

Password Strength Checker

Multi-Password Batch Strength Checker (C++), designed to check multiple passwords at once, show individual strength, and provide a summary report.

C++
View Project
VPN Connectivity verification in C Tutorial

VPN Connectivity verification in C

Efficient C program to verify VPN status, routing, and DNS configurations through comprehensive public IP and network adapter analysis.

C
View Project