How to Serve a Web App With NGINX, uWSGI and Flask. And Why You'd Want to.

So, you want to serve your Python app on the web? And you’re not sure how to? You know, you remind me a lot of myself. Seems like just yesterday that I was in your shoes.

And that’s because I was. Yesterday I had to patch and re-deploy a Flask app I’d thrown together a few years back. Patching was fine, but when it came around to re-deploying, I realized that I had no idea what I was doing - I’d originally deployed it by hastily copying and pasting code snippets from tutorials and Stack Overflow. But I hadn’t actually learned anything in the process.

So I went and did some research. My goal was to get a high level understanding of each component and its purpose. I’m glad I did - having a basic understanding of the network architecture behind my app made deploying it much, much easier. And I’d love to share what I learned with you.

In this post, we’ll answer the following questions:

  1. What are NGINX, uWSGI and Flask?
  2. How do they interact with each other? Why would I want to use all 3?
  3. How do I serve an app with them?

What are NGINX, uWSGI and Flask?

  • NGINX: A server application.
  • uWSGI: A server application that supports the WSGI calling convention.
    • WSGI: A convention for calling Python applications. Think of it as the language Python applications speak when communicating with the internet.
    • uwsgi: A protocol for transmitting WSGI calls.
  • Flask: A micro-framework for Python applications.

How do they interact with each other? Why would I want to use them?

Let’s say you were to click on this link for my app, AmazonFeatures. The browser on your local computer would open a new tab and send a request to the remote machine where the app is hosted. The request would be routed to Flask via NGINX, uWSGI, and app’s response would be returned in reverse. Here’s a diagram of that process:


So, why set it up this way? To understand, let’s walk through the details:

  • Your browser (the client) requests the app’s landing page.
  • This request is sent via https to the app’s host.
  • NGINX receives and decrypts the request.
  • NGINX forwards the request parameters via uwsgi to uWSGI.
  • uWSGI receives the request.
  • uWSGI extracts the WSGI parameters from the uwsgi request and calls the Flask app.
  • Flask executes with the parameters and produces a response.
  • The response is sent back to your browser through these steps in reverse.

You’re probably wondering why we’re using 2 server applications. So, why not use NGINX alone? We can’t. We need uWSGI because NGINX doesn’t support WSGI - thus, NGINX can’t send or receive information from Flask.

Why not use uWSGI alone? Convenience. NGINX is relatively easy to configure for SSL. It can also serve standard HTML and JS apps, and is easy to setup as a reverse proxy (a server that forwards traffic to other servers). For example - this site runs on the node based Ghost framework, and is served via NGINX. Since I already knew how to handle SSL with NGINX, it was easier for me to do so and forward traffic to uWSGI than learn how to with uWSGI.

How do I serve an app with these components?

I won’t go into the configuration details here. Digital Ocean has an excellent set of posts for this purpose, which you can read here:

However, I will cover the high-level steps:

  1. Get a remote host for your app (I use an AWS EC2 instance).
    • For security, it’s best to disable HTTP/HTTPS traffic for now.
  2. Ensure you have SSH access to your remote host.
  3. Install your app files.
  4. Ensure your app runs on the server.
    • Run your app on a secure port - I like to use localhost:8000
    • Check your app is running at the host address with ssh tunnel
  5. Install and configure NGINX and uWSGI.
    • Check that your setup works by navigating to your host’s IP address in your browser. You should see your app’s landing page.
  6. Change the A-record for your app’s domain. Wait for the change to propagate.
  7. Install certbot. Obtain certificates for your app domain.
    • Enable HTTP/HTTPS traffic if you disabled it earlier.
  8. Navigate to the domain in a browser. Make sure the app runs properly.
  9. Pat yourself on the back. Go get celebratory Nachos or something.

Closing Remarks

That's it! Before I end, I just want to encourage you to not be discouraged when something doesn't work. It's frustrating, but if you stick with it I'm sure you'll either solve it or come up with a workable solution. For example - I could never get uwsgi to run as a service as outlined in this Digital Ocean post. Instead, I run the app as a background process using tmux. It's hacky, but it works.

As always, I hope you learned something useful. Thanks for reading!

Written by