How to Deploy a Python Flask Application on Windows Server 2022

Flask is a lightweight Python web framework used for APIs, web applications, and microservices. Deploying Flask to production on Windows Server 2022 requires a WSGI server, a process manager, and a front-end web server to handle SSL and static files. This guide uses Waitress as the WSGI server, NSSM to run it as a Windows service, and IIS ARR as a reverse proxy.

Installing Python on Windows Server 2022

Download the latest Python 3.x installer (64-bit) from python.org/downloads/windows. Use the full installer, not the Microsoft Store version, for a server environment. During installation:

  • Check Add Python to PATH

  • Choose Customize installation and ensure pip and py launcher are selected

  • Install for All Users to make Python available system-wide

# Verify installation in a new PowerShell or CMD window
python --version
pip --version

# Example output:
# Python 3.12.4
# pip 24.1.2 from C:Python312Libsite-packagespip (python 3.12)

# List installed packages
pip list

If you need multiple Python versions on the same server, use the Python Launcher (py.exe):

# List available Python versions via py launcher
py --list

# Run specific version
py -3.11 --version
py -3.12 --version

Creating a Virtual Environment

Always use a virtual environment to isolate your application’s dependencies from the system Python. This prevents version conflicts and makes deployment reproducible.

# Create the application directory
mkdir C:appsmyflaskapp
cd C:appsmyflaskapp

# Create virtual environment
python -m venv venv

# Activate the virtual environment
venvScriptsactivate

# Your prompt will change to (venv) when active
# Verify you are using the venv Python
where python
# Should show: C:appsmyflaskappvenvScriptspython.exe

# Deactivate when done
deactivate

The virtual environment directory (venv) should not be committed to version control. Add it to .gitignore. Instead, maintain a requirements.txt file that lists all dependencies.

Installing Flask and Waitress

With the virtual environment activated, install Flask and Waitress. Waitress is a production-quality pure-Python WSGI server with excellent Windows support — unlike Gunicorn, which is Linux-only.

# Activate venv first
venvScriptsactivate

# Install Flask and Waitress
pip install flask waitress

# If you use a database ORM
pip install flask-sqlalchemy pyodbc

# If you use environment variable management
pip install python-dotenv

# Freeze dependencies to requirements.txt
pip freeze > requirements.txt

# On a fresh server, restore from requirements.txt
pip install -r requirements.txt

Running Flask with Waitress

A minimal Flask application entry point (app.py):

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '

Hello from Flask on Windows Server 2022

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

Run it with Waitress from the command line (virtual environment must be active):

# Basic usage — serves on all interfaces, port 5000
waitress-serve --host=127.0.0.1 --port=5000 app:app

# Specify the number of worker threads (default is 4)
waitress-serve --host=127.0.0.1 --port=5000 --threads=8 app:app

# If your Flask app object is inside a factory function (app factory pattern):
# waitress-serve --host=127.0.0.1 --port=5000 "mypackage:create_app()"

# Verify it is running
curl http://127.0.0.1:5000/health

Alternatively, call Waitress directly from Python code in a wsgi.py file. This is preferred when you need more programmatic control:

# wsgi.py
from waitress import serve
from app import app
import os

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    serve(app, host='127.0.0.1', port=port, threads=8)

# Run it:
# python wsgi.py

Configuring IIS as a Reverse Proxy (ARR/URL Rewrite)

Install IIS with the Application Request Routing (ARR) and URL Rewrite modules. Both are available from the Web Platform Installer or as standalone MSI downloads from Microsoft’s IIS download center.

After installation, enable proxy mode in ARR:

# In IIS Manager:
# 1. Select the server root node
# 2. Double-click "Application Request Routing Cache"
# 3. In the Actions pane, click "Server Proxy Settings"
# 4. Check "Enable proxy"
# 5. Click Apply

Create an IIS site pointing to a directory that contains only a web.config file. The site itself doesn’t need any application files — it just proxies to Waitress:



  
    
      
        
          
          
        
      
    

    
    
      
        
      
    
  

In Flask, configure it to trust the X-Forwarded-For header using ProxyFix:

# In app.py
from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

Creating a Windows Service Wrapper with NSSM

NSSM (Non-Sucking Service Manager) is a free utility that wraps any executable as a Windows service with automatic restart, stdout/stderr logging, and startup type control. Download from nssm.cc and extract to C:toolsnssm.

# Open elevated Command Prompt
cd C:toolsnssmwin64

# Install Flask/Waitress as a service
nssm install FlaskApp

# NSSM GUI opens. Configure:
#   Application Path:  C:appsmyflaskappvenvScriptspython.exe
#   Startup directory: C:appsmyflaskapp
#   Arguments:         wsgi.py

# To set these without the GUI:
nssm install FlaskApp "C:appsmyflaskappvenvScriptspython.exe" "wsgi.py"
nssm set FlaskApp AppDirectory "C:appsmyflaskapp"

# Configure logging
nssm set FlaskApp AppStdout "C:logsflaskapp-stdout.log"
nssm set FlaskApp AppStderr "C:logsflaskapp-stderr.log"
nssm set FlaskApp AppStdoutCreationDisposition 4   # append
nssm set FlaskApp AppStderrCreationDisposition 4   # append

# Rotate logs at 10 MB
nssm set FlaskApp AppRotateFiles 1
nssm set FlaskApp AppRotateBytes 10485760

# Set auto-restart on failure
nssm set FlaskApp AppExit Default Restart
nssm set FlaskApp AppRestartDelay 5000             # 5 second delay

# Set service start type
nssm set FlaskApp Start SERVICE_AUTO_START

# Start the service
nssm start FlaskApp

# Check status
nssm status FlaskApp

# Stop, restart
nssm stop FlaskApp
nssm restart FlaskApp

# Remove the service
nssm remove FlaskApp confirm

Environment Variables for Flask

Flask reads configuration from environment variables. Never hard-code secrets. Set system environment variables for the service account running NSSM:

# Set environment variables for the NSSM service (persists in service config)
nssm set FlaskApp AppEnvironmentExtra "SECRET_KEY=your-secret-key-here"
nssm set FlaskApp AppEnvironmentExtra "DATABASE_URL=mssql+pyodbc://user:pass@server/dbname?driver=ODBC+Driver+17+for+SQL+Server"
nssm set FlaskApp AppEnvironmentExtra "FLASK_ENV=production"

# Multiple env vars:
nssm set FlaskApp AppEnvironmentExtra "SECRET_KEY=abc123" "DATABASE_URL=..." "MAIL_SERVER=smtp.example.com"

# Alternatively, use a .env file with python-dotenv in wsgi.py:
# from dotenv import load_dotenv
# load_dotenv(r'C:appsmyflaskapp.env')

Flask configuration in config.py:

import os

class ProductionConfig:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'fallback-not-for-production'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    DEBUG = False
    TESTING = False

Debug Mode Off in Production

Flask’s debug mode exposes an interactive debugger and enables auto-reload. Both are security vulnerabilities in production and must be disabled.

# NEVER do this in production:
# app.run(debug=True)

# Correct production start via wsgi.py (debug is always False with Waitress):
from waitress import serve
from app import app

# Ensure DEBUG is off — Waitress ignores Flask's debug setting
# but enforce it explicitly
app.config['DEBUG'] = False
app.config['TESTING'] = False

serve(app, host='127.0.0.1', port=5000, threads=8)

# Verify debug is off at runtime
print("Debug mode:", app.debug)   # Should print: Debug mode: False

Serving Static Files

In production, serve static files (CSS, JS, images) from IIS directly rather than through Flask/Waitress. This reduces load on the Python process and leverages IIS’s optimized static file serving:



  
    
      
        
        
          
          
        

        
        
          
          
        
      
    
  

Troubleshooting Windows-Specific Issues

Issue: Waitress fails to bind on port 5000 — Windows sometimes has port 5000 reserved for dynamic application use. Check reserved port ranges:

netsh interface ipv4 show excludedportrange protocol=tcp

# If 5000 is in an excluded range, use a different port:
waitress-serve --host=127.0.0.1 --port=8080 app:app

Issue: File path errors in Windows — Python on Windows accepts both forward slashes and backslashes in paths, but use os.path.join() or pathlib.Path for cross-platform compatibility:

import os
from pathlib import Path

# Correct cross-platform path building
log_dir = Path('C:/logs/myapp')
log_dir.mkdir(parents=True, exist_ok=True)

upload_path = os.path.join('C:\', 'apps', 'myflaskapp', 'uploads')

Issue: Encoding errors with Windows file I/O — Windows defaults to cp1252 or a local ANSI code page. Set UTF-8 explicitly:

# Set UTF-8 mode for Python on Windows
# Add to NSSM environment:
# PYTHONUTF8=1

# Or in code:
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

# When opening files:
with open('data.txt', 'r', encoding='utf-8') as f:
    content = f.read()

Issue: Windows Firewall blocking inbound connections to IIS

netsh advfirewall firewall add rule name="IIS HTTP"  protocol=TCP dir=in localport=80  action=allow
netsh advfirewall firewall add rule name="IIS HTTPS" protocol=TCP dir=in localport=443 action=allow

Deploying Flask on Windows Server 2022 with Waitress and NSSM gives you a stable, production-ready configuration that survives reboots, automatically restarts on crashes, and integrates cleanly with IIS for SSL and static file handling. The stack is entirely free, well-supported, and suitable for both small internal tools and high-traffic production applications.