DEV Community

Chandler
Chandler

Posted on • Originally published at chand1012.dev on

Flask HTTPS How to Host a Flask Server With Gunicorn and HTTPS

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Next, we will write the program for the app itself. Enter the following command:

nano app.py

Enter fullscreen mode Exit fullscreen mode

Then enter the following code in the script

from flask import Flask

app = Flask( __name__ )

@app.route('/')
def index():
    return "Hello from Flask!"

Enter fullscreen mode Exit fullscreen mode

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 

Enter fullscreen mode Exit fullscreen mode

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 

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Then allow the file to be executable with:

chmod +x /var/www/flask/hello-world/start.sh

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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;
    }
}

Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
biofool profile image
Kenneth Kron

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

    <a href="{{ url_for('auth.register') }}">Register</a>
Enter fullscreen mode Exit fullscreen mode

works (stays on the correct base url and port
but

redirect url_for('auth.register') 
Enter fullscreen mode Exit fullscreen mode

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

Collapse
 
biofool profile image
Kenneth Kron

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:

        location @proxy_to_app {
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header HOST $http_host;
                proxy_pass http://127.0.0.1:8080/$request_uri;
                include proxy_params;
                proxy_redirect off;
}
Enter fullscreen mode Exit fullscreen mode

This function:

@bp.route("/logout")
def logout():
    """Clear the current session, including the stored user id."""
    session.clear()
    return render_template("index.html")
Enter fullscreen mode Exit fullscreen mode

Sends the user to a 500 error code.