Flask is small and approachable, so many people write their first web app with it. Locally, app.run(debug=True) at the end of the file starts the app and reloads it on every change. That built-in server is not meant for production, and debug mode exposed to the internet becomes a serious security hole.
Why app.run() is not used on servers
app.run() starts Werkzeug's development server, which is fine for local testing but not designed for concurrent load, crash recovery or hardened security. With debug=True, error pages include an interactive console that can execute arbitrary Python on the server; even though it is PIN-protected, it should never be reachable from the internet. On a server, always run the app through a proper WSGI server with debug off.
As an example, take misol.uz, the site of a wedding hall in Fergana, with hall photos, a price page and a date-based booking form backed by a small SQLite database. The developer built it in a week; now it has to go live without breaking anything.
Give the app a clear entry point
# app/__init__.py
import os
from flask import Flask
def create_app():
app = Flask(__name__, instance_relative_config=True)
app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"]
app.config["DATABASE"] = os.path.join(app.instance_path, "booking.sqlite")
os.makedirs(app.instance_path, exist_ok=True)
from . import views
app.register_blueprint(views.bp)
return app
# wsgi.py
from app import create_app
app = create_app()
SECRET_KEY signs sessions and forms, so it must be long, random and kept out of the repository; generate one with python -c "import secrets; print(secrets.token_hex(32))". The SQLite file lives in instance, because a database placed under static could be downloaded through the browser.
Running under Passenger on shared hosting
Some hosting panels run Python apps with Phusion Passenger, which looks for passenger_wsgi.py in the project root and uses the object named application:
# passenger_wsgi.py
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
from app import create_app
application = create_app()
After updating code, tell Passenger to reload by creating or touching tmp/restart.txt inside the project. Not every plan supports Python, and setup can differ where it does, so check your plan and the available versions in the sayt.uz cabinet or with support. On a PHP-only plan, a VPS is the better home for Flask.
Gunicorn and Nginx on a VPS
python3 -m venv venv
venv/bin/pip install -r requirements.txt gunicorn
venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 wsgi:app
Wrap this command in a systemd service so it survives reboots and restarts after crashes. Nginx sits in front, terminates HTTPS, serves /static/ from disk and forwards everything else with proxy_pass http://127.0.0.1:8000;.
Seeing the real address and protocol behind a proxy
Behind a proxy, Flask sees every request as plain HTTP from 127.0.0.1, so url_for(..., _external=True) builds http:// links and logs show one IP for everyone. Werkzeug's ProxyFix reads the forwarded headers:
from werkzeug.middleware.proxy_fix import ProxyFix
app = create_app()
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
The numbers state how many proxies sit in front; never set them higher than reality, or clients can spoof their IP. Once HTTPS works, also set SESSION_COOKIE_SECURE = True.
Post-deployment checklist
Open a non-existent page and confirm the error page shows no code or console. Submit a booking: attempt to write a readonly database means the app cannot write to the SQLite folder, which you fix with permissions. Finally, check that images and CSS load and that HTTP redirects to HTTPS. These checks take minutes and spare the wedding hall lost bookings at the start of the season.