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:

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!

 
 
media,300x250,212828891
media,300x250,860715515
media,300x250,948332563
media,300x250,208410624
media,300x250,563126446
media,300x250,532871659
media,320x50,373180341
media,320x50,745406434

Suenotek Blog

Seattle, Washington

Cookies | Privacy | Policy

About | Contact Us