Ubuntu 22.10: hosting a Python Flask web API with Gunicorn and Nginx.
The Python Flask web API is a simple web API to search for Australian postcodes based on locality aka suburb. With a newly installed Ubuntu 22.10 environment, we will go through the steps to host this web API using Gunicorn and Nginx.
Ubuntu 22.10: hosting a Python Flask web API with Gunicorn and Nginx. |
In this post Python: A simple web API to search for Australian postcodes based on locality aka suburb, I’ve developed a complete working web API. I’m discussing the steps to host this web API on Ubuntu 22.10 (Kinetic Kudu), using Gunicorn and Nginx.
The final content of this post has been written based on a fresh installation of Ubuntu 22.10. The firewall was disabled by default. We enable it:
$ sudo ufw enable
Verify that it has been enabled via:
$ sudo ufw status
The Ubuntu 22.10 machine name (host name) is
hp-pavilion-15
, its IP address is 192.168.0.17
.
These two identifiers will be used throughout this post.
The first draft of this post was based on my existing Ubuntu 22.10 installation,
whereby accessing the final site was working for both hp-pavilion-15
and 192.168.0.17
. Then I did something which damaged Ubuntu, and
I don’t know how to recover, so did a fresh reinstallation: hp-pavilion-15
is no longer working!
Table of contents
- Install required software
- Nginx configuration -- /etc/nginx/nginx.conf
- About
user behai;
in /etc/nginx/nginx.conf - Set up the web API project in Ubuntu 22.10
- Configure the virtual host service for the web API
- Assign port 82 to the web API virtual host
- Restart Nginx
- Test the final virtual host from Windows 10
- Current firewall status
- Concluding remarks
Install required software
We need to install some Python related tools
, Nginx
and git
. Run the following commands:
$ sudo apt-get update
$ sudo apt-get install python3-pip python3-dev nginx
$ sudo apt install python3-virtualenv
$ sudo apt install git
Nginx configuration -- /etc/nginx/nginx.conf
We make two changes to Nginx configuration file /etc/nginx/nginx.conf
:
-
user behai;
--user
changed tobehai
. (I.e. commented outuser www-data;
.)behai
is the user I use to log into Ubuntu. -
Enable
server_names_hash_bucket_size 64;
-- remove comment#
.
Using nano
to edit the Nginx config file
/etc/nginx/nginx.conf
:
$ sudo nano /etc/nginx/nginx.conf
# user www-data;
user behai;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
...
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;
server_names_hash_bucket_size 64;
...
Check Nginx configuration:
$ sudo nginx -t
If everything is okay, the output should be as follows:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Restart Nginx and verify its status with the following commands:
$ sudo systemctl restart nginx
$ systemctl status nginx.service
Opens both port 80
and port 443
to
allow both unencrypted and encrypted web traffic:
$ sudo ufw allow 'Nginx Full'
Nginx should now be ready, default site should be available. From Windows 10, I can get to the Nginx default site with http://hp-pavilion-15 and http://192.168.0.17:
About user behai;
in /etc/nginx/nginx.conf
Quoting
Deploying Gunicorn – a Gunicorn official documentation page:
www-data
is the default nginx user in debian, other distributions use different users (for example:http
ornginx
). Check your distro to know what to put for the socket user, and for the sudo command.
And see also this Stack Overflow answer, I DID ALSO ATTEMPT Joseph Barbere’s solution – DO NOT DO THAT! As a part of that suggested solution, I had to install SELinux, which is no longer compatible with Ubuntu 22.10; it damaged mine, I don’t know how to recover from the GRUB menu, that was why I did a fresh reinstallation.
Set up the web API project in Ubuntu 22.10
This step has been documented in GitHub Read Me. It is reproduced in this section.
Get the repo https://github.com/behai-nguyen/bh_aust_postcode.git
to directory /home/behai/webwork/bh_aust_postcode
. Its content should
look like the screenshot below:
Create the virtual environment venv
:
behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ virtualenv venv
To activate virtual environment venv
:
behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ source venv/bin/activate
Editable install project:
(venv) behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ venv/bin/pip install -e .
Install gunicorn
separately:
(venv) behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ venv/bin/pip install gunicorn
Download postcodes and write to SQLite database file:
(venv) behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ venv/bin/flask update-postcode
– Please note, the above step is only specific to this project.
Enable port 5000
:
$ sudo ufw allow 5000
Run the web server via gunicorn
:
(venv) behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ venv/bin/gunicorn --bind 0.0.0.0:5000 wsgi:app
At this point, the following should be accessible from Windows 10, or other devices connected to the Wifi network for that matter:
- 👎 http://hp-pavilion-15:5000/api/v0/ui -- it should work, but does not 👎!
- 👎 http://hp-pavilion-15:5000/api/v0/aust-postcode/spring -- it should work, but does not 👎!
- 🚀 http://192.168.0.17:5000/api/v0/ui -- it does work 🚀!
- 🚀 http://192.168.0.17:5000/api/v0/aust-postcode/spring -- it does work 🚀!
I have spent a lot of hours trying to get host name (hp-pavilion-15
)
to work, no avail so far: it does work if I turn off the firewall – but we don’t
want to do that. I think it has something to do with the socket, because we’ve seen
the default Nginx site can be accessed via hp-pavilion-15
. And also, I’ve
reinstalled Jenkins, and I can access it via
http://hp-pavilion-15:8080.
Stop the gunicorn
web server with Ctrl+C
, and
deactivate the virtual environment with:
(venv) behai@HP-Pavilion-15:~/webwork/bh_aust_postcode$ deactivate
Configure the virtual host service for the web API
Please note, the current working directory is still /home/behai/webwork/bh_aust_postcode
.
Run the test service command, this command will be used in the
service configuration file, test it first to ensure that it works:
$ /home/behai/webwork/bh_aust_postcode/venv/bin/gunicorn --workers 3 --bind unix:bh_aust_postcode.sock -m 007 wsgi:app
Output:
[2023-05-20 21:20:27 +1000] [5295] [INFO] Starting gunicorn 20.1.0
[2023-05-20 21:20:27 +1000] [5295] [INFO] Listening at: unix:bh_aust_postcode.sock (5295)
[2023-05-20 21:20:27 +1000] [5295] [INFO] Using worker: sync
[2023-05-20 21:20:27 +1000] [5296] [INFO] Booting worker with pid: 5296
[2023-05-20 21:20:27 +1000] [5297] [INFO] Booting worker with pid: 5297
[2023-05-20 21:20:27 +1000] [5298] [INFO] Booting worker with pid: 5298
Create the service file /etc/systemd/system/bh_aust_postcode.service
:
$ sudo nano /etc/systemd/system/bh_aust_postcode.service
This is the content, note the directories and the above command:
[Unit]
Description=bh-aust-postcode
After=network.target
[Service]
User=behai
Group=www-data
WorkingDirectory=/home/behai/webwork/bh_aust_postcode
Environment="PATH=/home/behai/webwork/bh_aust_postcode/venv/bin"
ExecStart=/home/behai/webwork/bh_aust_postcode/venv/bin/gunicorn --workers 3 --bind unix:bh_aust_postcode.sock -m 007 wsgi:app
[Install]
WantedBy=multi-user.target
Enable the service:
$ sudo systemctl start bh_aust_postcode
This command has no output. And the next command:
$ sudo systemctl enable bh_aust_postcode
And its output:
Created symlink /etc/systemd/system/multi-user.target.wants/bh_aust_postcode.service → /etc/systemd/system/bh_aust_postcode.service.
Assign port 82 to the web API virtual host
The virtual host configuration file is /etc/nginx/sites-available/bh_aust_postcode
,
create it with the command:
$ sudo nano /etc/nginx/sites-available/bh_aust_postcode
And its content is:
server {
listen 82;
server_name localhost;
location / {
include proxy_params;
proxy_pass http://unix:/home/behai/webwork/bh_aust_postcode/bh_aust_postcode.sock;
}
}
We’re assigning port 82
to this virtual host,
and get Nginx to forward requests to, effectively, /home/behai/webwork/bh_aust_postcode
.
For explanation on proxy_pass
, please see
NGINX Reverse Proxy
Next, we must enable the virtual host. Verify that it has not been enabled:
$ sudo ls -l /etc/nginx/sites-enabled
In the output, there should not be an entry for
/etc/nginx/sites-available/bh_aust_postcode
.
Enable it by creating a link to sites-enabled
:
$ sudo ln -s /etc/nginx/sites-available/bh_aust_postcode /etc/nginx/sites-enabled
Verify to ensure it has been enabled, i.e. there should be a symlink:
$ sudo ls -l /etc/nginx/sites-enabled
Among the entries, there should be an entry like the below:
lrwxrwxrwx 1 root root 43 May 20 10:22 bh_aust_postcode -> /etc/nginx/sites-available/bh_aust_postcode
See Difference in sites-available vs sites-enabled vs conf.d directories (Nginx)? for more discussion on this topic.
Open incoming TCP port 82
to all IP addresses:
$ sudo ufw allow from any to any port 82 proto tcp
Output:
Rule added
Rule added (v6)
Restart Nginx
Given that we’ve verified that Nginx configuration valid, we can now restart it, via:
$ sudo systemctl restart nginx
The above command has no output. We should check out the status of Nginx, with:
$ systemctl status nginx.service
We can see that it is active (running)
, that means we have it working:
● 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 Sat 2023-05-20 10:38:03 AEST; 5s ago
Docs: man:nginx(8)
Process: 4154 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 4155 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 4156 (nginx)
Tasks: 5 (limit: 9363)
Memory: 4.8M
CPU: 26ms
CGroup: /system.slice/nginx.service
├─4156 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
├─4157 "nginx: worker process"
├─4158 "nginx: worker process"
├─4159 "nginx: worker process"
└─4160 "nginx: worker process"
May 20 10:38:03 HP-Pavilion-15 systemd[1]: Starting A high performance web server and a reverse proxy server...
May 20 10:38:03 HP-Pavilion-15 systemd[1]: Started A high performance web server and a reverse proxy server.
The simple web API to search for Australian postcodes based on locality virtual host setup is now completed.
Test the final virtual host from Windows 10
At this point, the following should be accessible from Windows 10, or other devices connected to the Wifi network for that matter:
- 👎 http://hp-pavilion-15:82/api/v0/ui -- it should work, but does not 👎!
- 👎 http://hp-pavilion-15:82/api/v0/aust-postcode/spring -- it should work, but does not 👎!
- 🚀 http://192.168.0.17:82/api/v0/ui -- it does work 🚀!
- 🚀 http://192.168.0.17:82/api/v0/aust-postcode/spring -- it does work 🚀!
The following screenshots show the API in action. I took these using the previous Ubuntu installation, which was still version 22.10. I did have the host name working (as shown) then, but not now:
Now, the problem is:
Current firewall status
The current status of my firewall.
$ sudo ufw status verbose
[sudo] password for behai:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
445 ALLOW IN Anywhere
5000 ALLOW IN Anywhere
80,443/tcp (Nginx Full) ALLOW IN Anywhere
8080 ALLOW IN Anywhere
82/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
445 (v6) ALLOW IN Anywhere (v6)
5000 (v6) ALLOW IN Anywhere (v6)
80,443/tcp (Nginx Full (v6)) ALLOW IN Anywhere (v6)
8080 (v6) ALLOW IN Anywhere (v6)
82/tcp (v6) ALLOW IN Anywhere (v6)
Port 5000
which’s been enabled previously is no longer required, it could be deleted.
Concluding remarks
Prior to this post, I have set up several other virtual hosts with my original Ubuntu 22.10 installation, when I started this post, it was probably behind with some updates… When it got damaged, the first reinstallation I did was Ubuntu 23.04 (Lunar Lobster), I could not get host name to work. I reversed back to this 22.10 version, this time, I upgraded all packages, and host name is still not working for this virtual host.
It’s been an interesting exercise… I could not get what I wanted, but it’s been a useful exercise for me, regardless. I’m sure something will turn up, and I will able to fix this annoying problem in the future.
I hope you find the info useful. Thank you for reading and stay safe as always.
✿✿✿
Feature image sources:
- https://in.pinterest.com/pin/337277459600111737/
- https://www.pngwing.com/en/free-png-azefz/download
- https://icon-icons.com/download/170045/PNG/512/
- https://seeklogo.com/vector-logo/332789/python
- https://flask-restx.readthedocs.io/en/latest/
- https://www.vectorstock.com/royalty-free-vector/australia-map-with-flag-blue-red-background-vector-25323215
- https://logos-world.net/australia-post-logo/