1. Don't run the development server
flask run and app.run() start Werkzeug's development server. It is fine on your own machine and wrong on a server: it handles requests one or two at a time, and with debug=True its error page includes a debugger that can run code on the machine. Never let anyone else reach an app started with debug=True.
In production, run the app under gunicorn, a WSGI server that keeps a few worker processes and restarts any that crash. Add it next to Flask in requirements.txt:
flask
gunicorn
python-dotenv2. Listen where the host tells you
A host gives your program a port, usually in an environment variable, and expects it to listen on every interface (0.0.0.0), not just localhost. An app bound to 127.0.0.1 starts fine and nobody can reach it. On SnowServers the port is in SERVER_PORT; elsewhere it is often PORT.
Keep the Flask app in app.py as usual, and add a small launcher that starts gunicorn on that port. Calling it main.py means it is the file that gets run:
# main.py: starts the app under gunicorn
import os
import sys
port = os.environ.get("SERVER_PORT") or os.environ.get("PORT") or "8000"
os.execv(sys.executable, [
sys.executable, "-m", "gunicorn",
"--workers", "2",
"--bind", f"0.0.0.0:{port}",
"--access-logfile", "-",
"app:app",
])app:app means "the variable app in app.py". Two workers suit a 1 GB plan; the usual rule is two per CPU core plus one, as long as the memory allows. Each worker is a full copy of your app.
3. Keep secrets out of the code
Your SECRET_KEY, database password and API keys belong in a .env file next to the app, not in the code or in Git:
# app.py
import os
from dotenv import load_dotenv
from flask import Flask
load_dotenv()
app = Flask(__name__)
app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]Add .env to .gitignore. Flask signs its session cookie with SECRET_KEY, so a leaked key lets someone forge sessions: generate a long random one with python -c "import secrets; print(secrets.token_hex(32))".
4. Tell Flask it is behind HTTPS
When HTTPS is handled in front of your app, Flask sees plain HTTP and builds http:// links, redirects to the wrong scheme and logs the proxy's address instead of the visitor's. The proxy says what really happened in X-Forwarded-* headers; ProxyFix makes Flask believe them:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)Only do this when there is exactly one proxy in front that sets those headers, as there is on SnowServers. Without a proxy, anyone could send fake ones.
5. Test it the way the server will run it
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
SERVER_PORT=8000 python main.pyGunicorn doesn't run on Windows; there, test with flask run and leave gunicorn to the server, or use WSL. If http://localhost:8000 answers, the host will too.
Hosting it on SnowServers
- Choose a plan on the website plans page and pick A website and Python at checkout. Flurry, at 1 GB, runs most Flask apps.
- Upload
app.py,main.py,requirements.txt, your templates and static files in Files, or paste your GitHub repository into Git repository on the Startup tab. Put.envin by hand; it never goes in Git. - Restart from the console. It shows the packages installing,
python main.pystarting, and gunicorn's workers booting. - On the Website tab, add your domain and the two DNS records it shows. HTTPS follows on its own, usually within a few minutes.
Gunicorn replaces a worker that dies, and the whole app is started again if it exits. The website docs cover the rest, including deploying on every push.
Common problems
- "This site is not answering": the app is listening on
127.0.0.1or the wrong port. Bind to0.0.0.0andSERVER_PORT. WORKER TIMEOUTin the log: a request took longer than gunicorn's 30 seconds. Make the slow work faster or move it to a background job;--timeout 60buys time but hides the problem.- Links and redirects go to
http://:ProxyFixis missing. - Out of memory: fewer workers, or a bigger plan. The Crashes tab shows memory over the last day.