In this tutorial, we will be setting up a Flask server using Gunicorn and NGINX on Ubuntu 18.04 LTS.
Requirements
- Any system running Ubuntu 18.04 LTS with SSH enabled.
- An SSH client.
Installing
After connecting via SSH to your server as root, run the following commands to install the required programs:
apt update
apt upgrade -y
apt install nginx python3 python3-pip python3-venv
This will install Python, NGINX, and the virtual environment needed to run the app. It will also install PIP to download the additional packages. After that concludes we can move on with setting up the application itself.
Setup
Network
Before continuing, we want to restrict port 8080 incoming so that only NGINX can access Gunicorn. To do that, execute the following command:
iptables -A INPUT -p tcp --destination-port 8080 -j DROP
This will prevent your users from accessing the app without going through the NGINX proxy.
Flask Setup
Next we need to set up the file structure for the application. Execute the following in order:
mkdir -p /var/www/flask
cd /var/www/flask
python3 -m venv hello-world
source hello-world/bin/activate
cd hello-world
pip3 install gunicorn flask
Next, we will write the program for the app itself. Enter the following command:
nano app.py
Then enter the following code in the script
from flask import Flask
app = Flask( __name__ )
@app.route('/')
def index():
return "Hello from Flask!"
Gunicorn
Next we need to run the app with Gunicorn. To do this, execute the following command:
gunicorn -b 127.0.0.1:8080 app:app
This will start the app on localhost only with the port 8080. If you wish to run the app with multithreading enabled, specify a number of workers with the -w
flag:
gunicorn -w 2 -b 127.0.0.1:8080 app:app
Gunicorn’s Documentation recommends that you use between two and four workers per core on your server.
Use Ctrl+C
to exit the application.
Now, most people do not want to SSH into their server every time you want people to access your web app, so we will be making a service file to run the app on boot of the server. Execute the following command to create the file:
nano /lib/systemd/system/flask.service
Paste the following configuration in to the file:
[Unit]
Description=Gunicorn Flask Application
After=network.target
After=systemd-user-sessions.service
After=network-online.target
[Service]
User=root
Type=simple
ExecStart=/var/www/flask/hello-world/start.sh
TimeoutSec=30
Restart=on-failure
RestartSec=15
StartLimitInterval=350
StartLimitBurst=10
[Install]
WantedBy=multi-user.target
This will enable launch of the app on boot of your server using systemd
. NGINX is already configured to do this when you install it. Now we need the start.sh
file. Run the following to create the file:
nano /var/www/flask/hello-world/start.sh
Then paste this bash code to launch the app:
#!/bin/bash
echo Starting Flask example app.
cd /var/www/flask
source hello-world/bin/activate
cd hello-world
gunicorn -w 2 -b 127.0.0.1:8080 app:app
Then allow the file to be executable with:
chmod +x /var/www/flask/hello-world/start.sh
In the last line of the start.sh
file, the number 2
can be replaced with 2-4 times the number of cores on your server. Finally, to enable startup of the application on boot, execute the following:
systemctl enable flask
NGINX
Next we need to set up NGINX. NGINX allows us to set up a reverse proxy that redirects traffic to our app being hosted with Gunicorn.
First deactivate the default config, then change to the sites-enabled
directory and create a new file:
rm /etc/nginx/sites-enabled/default
cd /etc/nginx/sites-enabled
nano reverse-proxy.conf
Paste the following configuration into the file:
server {
listen 80;
listen [::]:80;
server_name _;
access_log /var/log/nginx/reverse-access.log;
error_log /var/log/nginx/reverse-error.log;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_pass http://127.0.0.1:8080;
proxy_redirect off;
}
}
After restarting nginx with systemctl restart nginx
, you have a working app hosted on your server. Navigate to the app IP address and you should see Hello from Flask!
in the top left corner of your favorite browser. This app will function as normal, but is especially susceptible to attacks as it does not have HTTPS. If you wish to configure that as well, continue along.
Adding HTTPS
We will be using a self-signed certificate on your application. This is adequate for testing, but you will want a key signed by a Certificate Authority, like Let’s Encrypt, later down the line.
Generating the Key
To create a self signed cert, execute the following command:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/selfsigned.key -out /etc/ssl/certs/selfsigned.crt
You can change the number after the -days
flag to whatever number you want, and it will last that long. If you are only using this key for testing, one year should be fine. Follow the on screen prompts to finish signing your own key.
Next, we need to generate a Diffie-Hellman group, which is used for additional security. This will take about 20 minutes on a server.
openssl dhparam -out /etc/nginx/dhparam.pem 4096
Adding to NGINX
To add to the web server, you will have to change the server configs. Remove the config you created previously and edit the configuration file as you did before:
rm /etc/nginx/sites-enabled/reverse-proxy.conf
nano /etc/nginx/sites-enabled/reverse-proxy.conf
Then paste the following config into your file:
server {
listen 80;
listen [::]:80;
server_name example.com;
return 302 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /etc/ssl/certs/selfsigned.crt;
ssl_certificate_key /etc/ssl/private/selfsigned.key;
ssl_dhparam /etc/nginx/dhparam.pem;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_pass http://127.0.0.1:8080;
proxy_redirect off;
}
}
Before closing and saving, replace example.com
with the IP Address of your server, or if you have an A record DNS pointing to your server’s IP, use that instead. After this is complete, restart NGINX with systemctl restart nginx
.
There will be a warning that comes up on your browser saying that your website is dangerous, this is just warning you that the certificate was not signed by a real authority and can be ignored. You now have a functioning reverse proxy with HTTPS for your Flask server.
Top comments (2)
OK the code sample in my question is not a good example. My issue is that I can't use url_for in flask as the redirect url sent to the browser is an http redirect. I've simplified the logic of my application to only use render_template and those all work.
Interestingly
works (stays on the correct base url and port
but
in python does not.
I've seen proxy_fix and other techniques for teaching flask to modify it's url_for generator but I haven't explored those yet.
relative hrefs in
This get's the app up and running but I'm having issues with app routing.
The app runs fine if I'm connected to port 5000 but when I put it behind nginx as a reverse proxy the logout link which is defined in flask as
With the following location:
This function:
Sends the user to a 500 error code.