Python Flask App on Raspberry Pi
Deploy a Python Flask web app on your Raspberry Pi. Covers Gunicorn, Nginx reverse proxy, and systemd service setup.
Introduction
Flask is lightweight and runs well on Raspberry Pi. This guide covers deploying a Flask app with Gunicorn as WSGI server, Nginx as reverse proxy, systemd for auto-restart, and SSL with certbot.
Prerequisites
- Raspberry Pi 4 or 5
- Raspberry Pi OS (Bullseye or newer)
- SSH access
- Domain name pointed to your Pi (for SSL)
- Python 3.9+
Step 1 — Create Flask App with Virtual Environment
Set up an isolated Python environment:
mkdir -p ~/myflaskapp && cd ~/myflaskapp
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn
Create a simple Flask app (app.py):
from flask import Flask, jsonify, request
import os
app = Flask(__name__)
@app.route('/')
def hello():
return jsonify(message='Hello from Flask on Pi')
@app.route('/status')
def status():
return jsonify(
status='ok',
uptime=os.popen('uptime').read().strip()
)
@app.route('/api/echo', methods=['POST'])
def echo():
data = request.get_json()
return jsonify(echo=data)
if __name__ == '__main__':
app.run(debug=False)
Test locally:
python app.py
# Visit http://localhost:5000
Press Ctrl+C to stop.
Step 2 — Create .env File for Configuration
Store secrets and config in .env:
cat > .env <<'EOF'
FLASK_ENV=production
FLASK_DEBUG=0
SECRET_KEY=your-secret-key-here-change-this
DATABASE_URL=sqlite:///app.db
EOF
# Secure it
chmod 600 .env
Update app.py to load from .env:
import os
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
# ... rest of your routes
Install python-dotenv:
pip install python-dotenv
pip freeze > requirements.txt
Step 3 — Set Up Gunicorn
Gunicorn runs Flask in production. Create a config file (gunicorn_config.py):
import multiprocessing
bind = "127.0.0.1:8000"
workers = 2 # Pi 4: 2 workers, Pi 5: 4 workers
worker_class = "sync"
worker_connections = 100
max_requests = 1000
max_requests_jitter = 100
timeout = 30
keepalive = 2
For Pi 4 with 4GB RAM, 2 workers is safe (~80MB per worker). Pi 5 can handle 4 workers.
Test Gunicorn locally:
source venv/bin/activate
gunicorn -c gunicorn_config.py app:app
You should see [8000] in logs. Test with curl in another terminal:
curl http://127.0.0.1:8000/
Stop with Ctrl+C.
Step 4 — Configure Nginx as Reverse Proxy
Install Nginx:
sudo apt install nginx -y
sudo systemctl start nginx
Create /etc/nginx/sites-available/myflaskapp:
upstream flask_app {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name mypi.local mypi.example.com;
client_max_body_size 10M;
location / {
proxy_pass http://flask_app;
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_read_timeout 30s;
proxy_connect_timeout 30s;
}
location /static/ {
alias /home/pi/myflaskapp/static/;
expires 30d;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/myflaskapp /etc/nginx/sites-enabled/
sudo nginx -t # Test config
sudo systemctl reload nginx
Test: Visit http://your-pi-ip/ in a browser.
Step 5 — Create Systemd Service for Auto-Start
Create /etc/systemd/system/myflaskapp.service:
[Unit]
Description=Flask Application
After=network.target
[Service]
Type=notify
User=pi
WorkingDirectory=/home/pi/myflaskapp
Environment="PATH=/home/pi/myflaskapp/venv/bin"
EnvironmentFile=/home/pi/myflaskapp/.env
ExecStart=/home/pi/myflaskapp/venv/bin/gunicorn -c gunicorn_config.py app:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
KillSignal=SIGTERM
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable myflaskapp
sudo systemctl start myflaskapp
sudo systemctl status myflaskapp
View logs:
sudo journalctl -u myflaskapp -f
Step 6 — Add SSL with Certbot
Secure your domain with free HTTPS:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot certonly --nginx -d mypi.example.com
Follow prompts. Certbot renews automatically via systemd timer.
Update /etc/nginx/sites-available/myflaskapp to redirect HTTP to HTTPS:
server {
listen 80;
server_name mypi.example.com;
return 301 https://$server_name$request_uri;
}
upstream flask_app {
server 127.0.0.1:8000;
}
server {
listen 443 ssl http2;
server_name mypi.example.com;
ssl_certificate /etc/letsencrypt/live/mypi.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mypi.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
client_max_body_size 10M;
location / {
proxy_pass http://flask_app;
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_read_timeout 30s;
proxy_connect_timeout 30s;
}
location /static/ {
alias /home/pi/myflaskapp/static/;
expires 30d;
}
}
Reload:
sudo systemctl reload nginx
Step 7 — Benchmark Request Performance
Test throughput with Apache Bench:
sudo apt install apache2-utils -y
# 1000 requests, 10 concurrent
ab -n 1000 -c 10 http://127.0.0.1/
# With SSL (https)
ab -n 1000 -c 10 https://mypi.example.com/
Typical Pi 4 results:
- Requests per second: 150–250 (depending on app complexity)
- Mean latency: 40–70 ms
- Variance: High under 10+ concurrent (single-threaded app limits)
Increasing Gunicorn workers to 4 doubles throughput:
# In gunicorn_config.py
workers = 4 # If Pi has 4+ cores
Restart:
sudo systemctl restart myflaskapp
Troubleshooting
502 Bad Gateway error Gunicorn crashed or is unreachable. Check:
sudo systemctl status myflaskapp
curl http://127.0.0.1:8000/
If Gunicorn is down, restart and check logs: sudo journalctl -u myflaskapp -n 50.
High CPU usage, slow response times
Too few workers for load. Increase workers in gunicorn_config.py and restart. Monitor: top or htop.
Nginx "Connection refused" to upstream Gunicorn isn't listening on 127.0.0.1:8000. Verify bind address in gunicorn_config.py matches upstream in nginx.conf.
SSL certificate renewal fails Certbot needs port 80 open. Ensure firewall allows it. Test renewal:
sudo certbot renew --dry-run
Out of memory during requests
Reduce worker_connections from 100 to 50, or reduce workers to 1. Monitor memory: free -h.
Complete requirements.txt Reference
Save dependencies for reproducible deployments:
flask==3.0.0
gunicorn==21.2.0
python-dotenv==1.0.0
Restore on another Pi:
pip install -r requirements.txt
Summary
Flask on Pi with Gunicorn and Nginx handles 150+ requests/second reliably. Systemd keeps it running 24/7 with auto-restart. SSL from certbot provides encrypted access. This stack—Flask, Gunicorn (2–4 workers), Nginx, systemd—scales to production homelabs, remote sensors, or internal APIs. Add monitoring (Netdata or Prometheus) to catch issues before they impact your homelab.