Gold unicorn for Python Flask Apps

Normally you can serve a Python Flask app using Gunicorn like this:

gunicorn -b app:app

This binds all network traffic ( on port 8080 to program’s app object. First one is the file name (without py extension) and second one is the app object inside the Python code.

Normally you can serve a Python Flask app using Gunicorn like this:

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Here is a common structure of a flask app:

Using Gunicorn with Pm2

With pm2 usage of gunicorn is slightly more complicated.

pm2 start "gunicorn -b" app:app

Or giving the app a name for monitoring purposes like below.

pm2 --name=PythonApp start "gunicorn -b" app:app

The difference between using and localhost as the bind address in the Gunicorn command can affect the accessibility of the server.

When you use gunicorn -b app:app, Gunicorn binds to all network interfaces, allowing connections from any IP address, including external IP addresses. This makes your server accessible from both the local machine and other machines on the network.

However, when you use gunicorn -b localhost:8000 app:app, Gunicorn only binds to the loopback interface, also known as This means that the server is only accessible from the local machine itself. Connections from other machines on the network or external IP addresses will be blocked.

Add Ingress Rule to allow Inbound Traffic

You should configure the server to allow Inbound traffic for specific port you’d like to use such as 8000, 8080, 80, 81, 3000, 5000 etc.

Port 80 is the universally accepted HTTP port and browsers automatically look for port 80 when a root domain URL or IP is accessed.

You need to open the port to listening under VCN (Virtual Cloud Networks) > Subnet > Default Security List for vcn-xyz > Add Ingress Rules

Listing Post Listening Activity

lsof -i -P -n

You can use lsof to show a list of files being served and filter it to internet files only and force it to show the ports instead of app names.

-i: Filters the output to show only Internet-related open files (network connections).
-P: Prevents the conversion of port numbers to service names.
-n: Skips the conversion of IP addresses to hostnames.

Alternatively, you can use netstat to do something similar:

netstat -tln  # Show only listening TCP ports
netstat -uln  # Show only listening UDP ports

If you don’t see your desired port here. First place to look is the firewall rules.

Additionally, you will want to allow your cloud instance or server to accept inbound traffic from those ports if you want to achieve access from other machines to the specific port.

A good test for this is to use curl command.


curl localhost:8080

works from inside ssh but you still can’t access 8080 through public ip that means inbound traffic is not being allowed.

Serving Flask App Using Gunicorn via Pm2

Even a classier way to serve Flask app is to generate a configuration JS file and instruct Pm2 to use gunicorn to serve the Flask app.

File can be created as below:

module.exports = { 
  apps: [ 
      name: 'Flaskmyapp', 
      script: 'gunicorn', 
      args: 'app:app -b', 
      interpreter: 'python', 
      interpreter_args: '-u', 
      instances: 1, 
      autorestart: true, 
      watch: false, 
      max_memory_restart: '1G', 
      env: { 
        FLASK_APP: '', 
        FLASK_ENV: 'production', 

Nginx Reverse Proxy

It’s common to reverse proxy with Nginx to specific ports. You can instruct Nginx to Reverse Proxy multiple ports, especially other than port 80, like this:

server {
  listen 80;
  server_name servername;

  location / {
    proxy_pass http://localhost:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

server {
  listen 81;
  server_name servername;

  location / {
    proxy_pass http://localhost:5000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

In this example port 80 on public IP will forward to port 3000 while port 81 on public IP forwards to port 5000.

Recommended Posts


  1. Thank you, Umut.

  2. Well, this article clears up some of the Gunicorn and PM2 confusion I’ve had. Python devops in general have been a bit of a head-scratcher for me, so thanks for shedding some light on it!

  3. if you are looking for the AWS equivalent of a Virtual Cloud Network, it’s called a Virtual Private Cloud (VPC) in AWS. In case someone is confused out there.

  4. The firewall stuff is crucial, but honestly, I’m nervous when tweaking security rules.

  5. Hey!, I just deployed my first Hello World Django App. Thank you so much for making this knowledge accesible!!

  6. I can’t be the only one who’s been puzzled by that ‘’ vs. ‘localhost’ thing, right? The article helps, but I still feel like I need a bit more practice to get it right.

  7. Using a JS file for Flask apps is intriguing, but I got a bit lost creating that file myself. A bit more step-by-step would have helped me out.

    • Hello,

      You can use JavaScript code with any html application, Flask, Django, PHP etc. they aren’t mutually exclusive with JavaScript.

      Here is a javascript example for you: A Flask app that uses JS code to show tooltip when hovered on certain text:


      document.addEventListener('DOMContentLoaded', function () {
          // Get the elements
          const hoverText = document.getElementById('hoverText');
          const infoPop = document.getElementById('infoPop');
          // Add event listeners
          hoverText.addEventListener('mouseenter', function () {
     = 'block';
          hoverText.addEventListener('mouseleave', function () {
     = 'none';

      HTML Page:

      <!DOCTYPE html>
      <html lang="en">
              <meta charset="UTF-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Simple Hover Example</title>
                  /* Add some basic styling for the pop-up */
                  .info-pop {
                      position: absolute;
                      background-color: #f1f1f1;
                      padding: 10px;
                      border: 1px solid #d4d4d4;
                      border-radius: 5px;
                      display: none;
              <script src="app.js"></script>
              <p id="hoverText">Hover me!</p>
              <!-- Pop-up element -->
              <div id="infoPop" class="info-pop">This is some information!</div>
  8. I’m encountering a 502 Bad Gateway error when trying to access my website. It seems like Nginx is struggling to connect to my application server, pretty frustrated tbh

    • Hi 502 Error usually boils down to generic issues with app not being served or wrong Nginx configuration. Take it step by step and simplify each step to troubleshoot. I.e.: You can try to serve a “Hello World!” app to ensure app isn’t the culprit.

      You can then review your Nginx config.
      Finally, it’s quite likely that pm2 is not starting the app properly if that’s what you’re using. If there’s a small mistake regarding app dir structure or dependencies etc., app won’t start properly and you’ll get 502 Bad Gateway Error.

      If you change Nginx config don’t forget to restart it:
      sudo systemctl restart nginx

      And keep in mind to check the logs which is a pro way of handling things:
      Error Logs:
      Nginx error logs can be found in the /var/log/nginx/ directory.

      sudo tail -f /var/log/nginx/error.log

      This command allows you to follow the error log in real-time. You can replace error.log with access.log if you want to view access logs.

      Access Logs:
      sudo tail -f /var/log/nginx/access.log

      And also:
      – pm2 logs
      – pm2 logs your_app_name
      – pm2 logs your_app_id

      Good luck!

  9. My Nginx access log is flooded with 403 Forbidden errors, but I can’t figure out why. I’ve checked the permissions, and everything seems fine. Any suggestions on troubleshooting this?

  10. Nginx is giving me an SSL handshake error when I try to access my site via HTTPS. What could be causing this SSL error?

  11. The Parallel Multithreaded Machine (PM2) is a fantastic process manager for all webapps, js, python, rust, doesn’t matter. I use these pm2 commands below on a daily basis//

    -pm2 start pm2 stop pm2 restart pm2 delete
    -pm2 list
    -pm2 monit

Add a Comment

Your email address will not be published. Required fields are marked *