Deploy Flask The Easy Way With Gunicorn and Nginx!
Tags: tutorial, python, nginx, flask
Published: 2021-01-29 04:57:12

# Introduction

After creating a Flask application you probably want people to see it. This tutorial will show you how to deploy your Flask application on a Linux server Nginx with Gunicorn. 

# Requirements

* A Server ([Debian](https://www.debian.org/) is used in this tutorial)
* Nginx
* Gunicorn
* Pipenv


The first step in the process is to install pip and pipenv. We will use pipenv to set up the virtual environment. 

```bash
$ sudo apt update
$ sudo apt upgrade

$ sudo apt install python3-pip pipenv
```



Check that pipenv installed correctly by checking the version.

```bash
$ pipenv --version
pipenv, version 2022.12.19
```

Create project directory to hold your project. `/var/www/` is a good location.

```bash
$ sudo mkdir -p /var/www/myproject
```

The default permissions for the directory are set to root.

```bash
$ ls -ld /var/www/myproject
drwxr-xr-x 2 root root 4096 Feb 20 12:37 /var/www/myproject/
```

Change the permissions for the user and group to your username and the group `www-data`.

```bash
$ sudo chown -R $USER:www-data /var/www/myproject
```

Check the to see that the permissions changed to username `brandon` and group name `www-data`.

```bash
$ ls -ld /var/www/myproject
drwxr-xr-x 2 brandon www-data 4096 Jan 27 12:37 /var/www/myproject/
```

CD into the /var/www/myproject directory.

```bash
$ cd /var/www/myproject
```

Create a `.env` file to hold the environmental variables.

```bash
$ touch .env
```

Edit the `.env` file to add the `FLASK_APP` and `FLASK_ENV` environmental variables. With any editor add these two lines:

`$ vim .env`

```bash
FLASK_APP=wsgi.py
FLASK_ENV=production
```

Start the virtual environment. Pipenv will load the variables in the `.env` file automatically.

```bash
$ pipenv shell

Loading .env environment variables...
Loading .env environment variables...
Creating a virtualenv for this project...
Pipfile: /var/www/myproject/Pipfile
Using /usr/bin/python3 (3.11.2) to create virtualenv...
⠇ Creating virtual environment...created virtual environment [...]
✔ Successfully created virtual environment!
Virtualenv location: /home/brandon/.local/share/virtualenvs/myproject-jyD3CuVy
Creating a Pipfile for this project...
Launching subshell in virtual environment...
```

Use pipenv to install the dependencies.

```bash
$ pipenv install flask gunicorn

Loading .env environment variables...
Installing flask...
Installing gunicorn...
Pipfile.lock not found, creating...
[...]
```

To set up a minimal Flask application create two files `application.py` and `wsgi.py`. The main Flask application will be stored in `application.py`. 
`wsgi.py` will be used to get the application running.

```bash
$ touch application.py wsgi.py
```

Edit `application.py` file. Add the code for a minimal Flask application with one route for the index page.

`$ vim application.py`

```python
from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    '''Index page route'''

    return '<h1>Application Deployed!</h1>'
```

Edit the wsgi.py file. Add this code wsgi.py to get the application running.

`$ vim wsgi.py`

```python
from application import app

if __name__ == '__main__':
    app.run(debug=False)
```

Let’s test run the Flask application!
Make the Flask application listen on all server interfaces by specifying the address ‘0.0.0.0’.

```bash
$ flask run --host '0.0.0.0'

 * Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
 * Serving Flask app 'wsgi.py'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.12.34:5000
Press CTRL+C to quit
```

Now you can access the application from _another_ computer using a browser, navigate to the server's IP address on port `5000`. Make sure port `5000` is not blocked by a firewall so that you will be able to access your Flask application. 

Since my server's IP address is `192.168.12.34` I access the Flask application from my laptop with a browser using this address:

`http://192.168.12.34:5000`

You should see this in the browser.

![flask-app1.png](https://i.postimg.cc/zXnrDzYQ/flask-app1.png)


Press `CTRL+C` to stop the Flask development server. 

It is time to set up Gunicorn!

If you were able to run the Flask development server successfully use this command to test run the application using Gunicorn.

```bash
(myproject) $ gunicorn --workers 4 --bind 0.0.0.0:5000 wsgi:app
```

`--workers N`

Set the --workers to two times the number of cores in your server. Adjust the number later if you have any issues. Do not exceed 12.

 `--bind 0.0.0.0:5000`

This will listen on all server networking interfaces on port 5000.

 `wsgi:app`

`wsgi` is the file name without the `.py` extension. `app` is the instance of the Flask application within the file. You should see the similar output below.

```bash
$ gunicorn --workers 4 --bind 0.0.0.0:5000 wsgi:app

[2024-02-20 20:57:21 -0500] [4936] [INFO] Starting gunicorn 21.2.0
[2024-02-20 20:57:21 -0500] [4936] [INFO] Listening at: http://0.0.0.0:5000 (4936)
[2024-02-20 20:57:21 -0500] [4936] [INFO] Using worker: sync
[2024-02-20 20:57:21 -0500] [4937] [INFO] Booting worker with pid: 4937
[...]
```

Press `CTRL+C` to stop the Gunicorn server.

While you are in the virtual environment check the path of `gunicorn`. Take note of this path. You will need to know the path to gunicorn to configure the systemd service file. My path is `/var/www/myproject/.venv/bin/gunicorn` your path might be `/home/$USER/.local/share/virtualenvs/myproject-544gQc4M/.venv/bin/gunicorn`. The path depends if you have `PIPENV_VENV_IN_PROJECT=true` set in your `.bashrc` file or not. If the variable is set to `true` pipenv will use the `.venv` in your project directory.

```
$ which gunicorn

/var/www/myproject/.venv/bin/gunicorn
```

Now that you have a basic Flask application running with Gunicorn we can set up Nginx.

# Install Nginx

Run these commands to install and start the Nginx webserver.

```bash
$ sudo apt install nginx

$ sudo systemctl enable nginx

$ sudo systemctl start nginx
```

Check to see if Nginx is running by running this command.

```bash
$ sudo systemctl status nginx

● nginx.service - A high performance web server and a reverse proxy server
     Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
     Active: active (running) since Tue 2024-02-20 21:03:17 EST; 16min ago
       Docs: man:nginx(8)
   Main PID: 5163 (nginx) 
   [...]
```

Now we create the systemd file that runs Gunicorn as a service. Add the following code but change all paths and user names so that they correspond to your set up. Pay careful attention to the `Environment=` and `ExecStart=` variables. The `Environment=` variable should lead to the `bin` directory inside your virtual environment. The `ExecStart=` path should lead where gunicorn is installed in the virtual environment.

```
$ sudo vim /etc/systemd/system/myproject.service
```

> [Unit]
> Description=`myproject`.service - A Flask application run with Gunicorn.
> After=network.target
> 
> [Service]
> User=`brandon`
> Group=www-data
> Environment="PATH=`/var/www/myproject/.venv/bin"`
> WorkingDirectory=/var/www/`myproject`/
> ExecStart=`/var/www/myproject/.venv/bin/gunicorn` --workers 3 \ 
> --bind unix:/var/www/`myproject/myproject`.sock wsgi:app
>
> [Install]
> WantedBy=multi-user.target

`User`

Sets the user who has permission to the project directory.

`Group`

Sets the group who has permission to the project directory.

`Environment`

Sets the path to the `bin` directory inside the virtual environment.

`WorkingDirectory`

Sets the base directory where the code for the project is.

`ExecStart`

Sets the path to the `gunicorn` executable inside the virtual environment along with the gunicorn command line options.


Now I enable and start the myproject systemd service.

```bash
$ sudo systemctl enable myproject

$ sudo systemctl start myproject
```

Next we need to create the server block/virtual host in Nginx. Create a file in `/etc/nginx/sites-available/` with any editor. Add the following content. Change the name `myproject`.conf to the name of your project.

```bash
$ sudo vim /etc/nginx/sites-available/myproject.conf
```

```bash
server {
        listen 80;
        server_name myproject www.myproject;

        access_log /var/log/nginx/myproject.access.log;
        error_log /var/log/nginx/myproject.error.log;

        location / {
                include proxy_params;
                proxy_pass http://unix:/var/www/myproject/myproject.sock;
        }
}
```

`server_name` is where the domain name goes. That is what you will use to access the web application.

`access_log` and `error_logs` specify the path to the access  and error logs.

`location` block is where Nginx reverses proxy back to the Flask application.

Enable the website by creating a link to the sites-enabled directory.

```bash
$ sudo ln -s /etc/nginx/sites-available/myproject.conf /etc/nginx/sites-enabled/
```

Make sure the link has been created in sites-enabled directory.

```bash
$ ls -l /etc/nginx/sites-enabled/ | grep myproject

lrwxrwxrwx 1 root root 41 Feb 20 17:37 myproject.conf -> /etc/nginx/sites-available/myproject.conf
```

Check the nginx configuration for errors then restart the service.

```bash
$ nginx -t

$ sudo systemctl restart nginx

$ sudo systemctl status nginx
```

The Flask application is no longer accessible via the IP address since it is now being served by Gunicorn and Nginx. To access the Flask application you would need to use the name you set in the Nginx server block for the directive `server_name` in the Nginx configuration. To access the web page can edit the host file on your desktop/laptop to point the domain to the IP address of your server.

Edit the host file to add point the domain name to the server.
Since my server's IP address is `192.168.12.34` I would add this line to the host file.

`192.168.12.34       myproject    www.myproject`

Host file location for Linux:
`/etc/hosts`

Host file location for Windows:
`C:\Windows\System32\drivers\etc\hosts`

Now I can access the Flask application with a browser via the name.

`http://myproject`

or

`http://www.myproject`

You should seen something similar below.

![flask-app2.png](https://i.postimg.cc/bwhDFB3X/flask-app2.png)


# Troubleshooting

## Here are some troubleshooting steps...

For when things do not go right there are a few things you can do to troubleshoot.

It is very important to check carefully for any spelling errors in all the files you edited.

Check systemd.

`$ sudo systemctl status myproject.service`

`$ sudo journalctl -xe`

Restart the Gunicorn systemd service.

```bash
$ sudo systemctl restart myproject.service
$ sudo systemctl daemon-reload
```

Add logging to Gunicorn by changing the `ExecStart` line. Add `--error-logfile error.log` and `--log-level debug` to the end of the line.

Change the `ExecStart` line:

```bash
ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app
```

To this:

 ```bash
ExecStart=/var/www/myproject/.venv/bin/gunicorn --workers 3 --bind unix:/path/to/myproject/myproject.sock wsgi:app --error-logfile error.log --log-level debug
```

Check the Nginx log files.

`$ less /var/www/nginx/error.log`

`$ less /var/www/myproject/error.log`

Check to see if the Nginx server is listening on port 80.

```bash
$ ss -lnt | grep 80
LISTEN    0         511                0.0.0.0:80               0.0.0.0:* 
LISTEN    0         511                   [::]:80                  [::]:* 
```

Use curl to check for a 200 OK response.

`$ curl -I http://myproject`

```bash
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 30
Server: Werkzeug/1.0.1 Python/2.7.16
Date: Wed, 03 Feb 2021 15:18:55 GMT
```

# Conclusion

You have learned to configure Gunicorn to run a Flask application. You learned how to run Gunicorn as a systemd service. You learned to configured a virtual host in Nginx. You should now be able to deploy a Flask application to a server.

Follow me on [Github](https://github.com/brandon-wallace).

Feel free to leave questions, suggestions, and comments.