Python web app via Flask, uWSGI and Nginx reverse proxy
Author: Paul Sueno
Created: 9/26/2021
Web development through Python with Flask module. Web content created on back end with uWSGI interface. Nginx used as front end reverse proxy.
Our Python web framework will use Flask. The built-in WSGI module in Flask is for debugging, not for deployment. We will couple Flask with uWSGI for this reason. Our public-facing web content will be served through an Nginx reverse proxy. Web content database will be PostgreSQL.
If you haven't already:
- Install a fully encrypted Ubuntu 20.04 server on the cloud
- Start a LEMP stack with Nginx
- Complete the LEMP stack with PostgreSQL and PHP
Python web framework intro - Flask
Web development allows user input from the browser to change web content the user sees. PHP has been one of the popular programming languages used. Python has been gaining ground, especially with its popularity in the space of analytics and its relative ease of use.
Unlike PHP, Python cannot create web content inherently. When coupled with a web framework, Python can do so. However, it has to do so through a WSGI (web server gateway interface). There are two popular web frameworks for Python, Django and Flask. Here's a comparison between the two web frameworks. We will use the latter.
Python web framework intro - uWSGI and Nginx
Flask by itself, however, cannot be used to host a public facing web site. It pretty much serves web content one-at-a-time, as requests come in. This can slow down your server real quick! To complete a Python web framework, we have to use WSGI. Flask has a very inefficient WSGI built-in, and so we use a separate WSGI called uWSGI.
A server that runs Python, Flask and uWSGI can serve web content. But it still is not very efficient or secure. Moreover, there just is not as much support on the web with WSGI and public-facing web server issues. These include things like using sub-domains, customizing headers, optimizing TLS parameters, and many more. For this reason, the two most popular web servers (Apache and Nginx) have developed their own WSGI interfaces. So to complete our Flask web framework, we will couple it with our Nginx server (as reverse proxy).
Python should already be installed on your Ubuntu server. Some use the older version of Python, and so there are two commands in Ubuntu. We will use the newer version, which is the python3
command. To check our version, run python3 -V
.
Python - virtual environment
Flask is a light-weight Python web framework (as opposed to full-stack). This just means we get to really customize it. No worries, lots of help out on the web for this.
Before installing Flask, let's talk about virtual environments. A Python virtual environment allows us to isolate an instance of Python with customized modules that won't affect the whole system. This way, the web development application stands alone on the server, isolated from the rest of the server.
Let's install some packages that will help us create our Python web framework virtual environment. We will actually install Flask within our virtual environment in a bit.
sudo apt update
sudo apt install python3-venv python3-pip python3-dev python3-setuptools build-essential libssl-dev libffi-dev
We're ready to create the virtual environment. We will house the application in its own directory. You are welcome to substitute mysite
with whatever name you want for your web application.
mkdir -p ~/web-apps/mysite
cd ~/web-apps/mysite
To create our the virtual environment, we first have to install the files in our directory. Type the following command in your Ubuntu user bash prompt. You can substitute mysite-env
with whatever name you want for your site's virtual environment.
python3 -m venv mysite-env
This creates a directory structure with all the files required to run our virtual environment. We can now activate our virtual environment.
source mysite-env/bin/activate
We are now in our virtual environment! You'll notice the command prompt has changed reflecting this. Your Ubuntu user prompt is now pre-fixed with the name of your virtual environment: (mysite-env) user@host:~/web-apps/mysite$
to the left of your usual Ubuntu user bash prompt. In case you ever wonder if you are or are not in your Python virtual environment, just look there.
To get out of the virtual environment, just type this command by itself:
deactivate
Install Flask and uWSGI
We will install Flask and uWSGI in our virtual environment. Be sure you see (mysite-env)
on the left of your prompt. Before we install Flask and uWSGI, we want to make sure all package dependencies are stored on our server. We will accomplish this through Python pip and wheels. Python's Pip is the barebones package installer, while wheels is more robust. Run pip install wheel
.
Now we can install Flask and uWSGI.
pip install uwsgi flask
Web app - create sample script
This blog is just not intended to teach you how to code Python or conventions on building a Python web application. Feel free to take a course, buy a book or Bing this information. Let's assume you have the basics down.
Let's go ahead and create a simple Python web application script. Be sure you see (mysite-env)
on the left of your prompt. Substitute mysite.py
with whatever name you want to use. Run nano ~/web-apps/mysite/mysite.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "<h1 style='color:purple'>Welcome to my web site!</h1>"
if __name__ == "__main__":
app.run(host='0.0.0.0')
Web app - uWSGI service
Ultimately, we want our Flask web app's virtual environment to be run as a service on our Nginx web server. To start, let's set up uWSGI and create a service for this.
We'll begin by creating an interface between Flask and uWSGI. Run nano ~/web-apps/mysite/wsgi.py
.
from mysite import app
if __name__ == "__main__":
app.run()
The configuration options for uWSGI will be stored in a file. Let's create it by running nano ~/web-apps/mysite/mysite.ini
. You should adjust the number of child processes controlled by uWSGI (processes = 5
) to fit your server specs. The more cores you have, the more children processes it can handle. Be sure to substitute mysite
with your own project's name.
[uwsgi]
module = wsgi:app
master = true
processes = 3
socket = mysite.sock
chmod-socket = 660
vacuum = true
die-on-term = true
Let's create the Systemd service configuration file. Run sudo nano /etc/systemd/system/mysite.service
. Be sure to read through this example file below, and substitute your own information (eg, Description=...
, [username]
, mysite
, mysite-env
and mysite.ini
).
[Unit]
Description=uWsgi instance to serve mysite Python web app.
After=network.target
[Service]
User=[username]
Group=www-data
WorkingDirectory=/home/[username]/web-apps/mysite
Environment="PATH=/home/[username]/web-apps/mysite/mysite-env/bin"
ExecStart=/home/[username]/web-apps/mysite/mysite-env/bin/uwsgi --ini mysite.ini
[Install]
WantedBy=multi-user.target
The service can now be started and enabled. And of course, the service status can be verified.
sudo systemctl enable mysite
sudo service mysite start
sudo service mysite status
Nginx reverse proxy
We will use Nginx as a reverse proxy. It is just so much more powerful and efficient as the public-facing web server. I'll assume you own your own domain. Be sure to substitute domain.com
with your own domain name. This is now a good time to go to your DNS name registrar and create A
and AAAA
records for your mysite.domain.com
web app. Again, substitute mysite
with your own app's name.
If you've followed along in my other tutorial blogs, I use TLS certificates via Let's Encrypt. For Let's Encrypt, we do need to create a static file structure for Nginx to serve. The site's actual content will be served through the Nginx interfacing with uWSGI service mysite.sock
. Let's create the static file directory structure for Let's Encrypt.
sudo mkdir -p /usr/share/nginx/mysite/.well-known/acme-challenge
sudo chown -R www-data:www-data /usr/share/nginx/mysite
We have to expand our Let's encrypt TLS certificate to include our new subdomain. I'll assume you have followed along and have several subdomains registered already. Be sure to modify the following command to fit your situation. It will break all your other sites if you don't do this right!!
Before typing the next command in, did you modify it correctly? You have been warned!!
sudo certbot certonly --expand --webroot \
-w /usr/share/nginx/html -d www.domain.com -d domain.com -d host.domain.com \
-w /usr/share/nginx/db -d db.domain.com \
-w /usr/share/nginx/mysite -d mysite.domain.com
We have to restart the services that rely on our TLS certificates. The following script was created in prior blogs.
sudo /etc/letsencrypt/renewal-hooks/post/servers-reload.sh
Create the Nginx configuration for our subdomian. Run sudo nano /etc/nginx/sites-available/mysite.domain.com.conf
.
server {
listen 80;
listen [::]:80;
server_name mysite.domain.com;
location ^~ /.well-known/acme-challenge/ { root /usr/share/nginx/mysite; }
location / { return 301 https://mysite.domain.com$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl http2;
server_name mysite.domain.com;
ssl_certificate /etc/letsencrypt/live/www.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.domain.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.domain.com/chain.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_stapling on;
ssl_stapling_verify on;
location / {
include uwsgi_params;
uwsgi_pass unix:/home/[username]/web-apps/mysite/mysite.sock;
}
}
Enable the Nginx configuration file.
sudo ln -s /etc/nginx/sites-available/mysite.domain.com.conf /etc/nginx/sites-enabled/
sudo service nginx restart
Test your site
Your site should now be up and running! Go check it out. I'd recommend typing in your browser http://mysite.domain.com
(substituting your own names of course). If Nginx is set up correctly, it will automatically redirect to https:
. If Flask and uWSGI are set up correctly, then you will see your script rendered web page.
Congratulations!