In this article, we’ll show how to set up an Ubuntu 20.04 server and install and configure the components required for deploying Python applications. We’ll configure the WSGI server Gunicorn to interact with our application. Gunicorn will serve as an interface that converts client requests via the HTTP protocol into Python function calls executed by the application. Then, we will configure Nginx as a reverse proxy server for Gunicorn, which will forward requests to the Gunicorn server. Additionally, we will cover securing HTTP connections with an SSL certificate or using other features like load balancing, caching, etc. These details can be helpful when working with cloud services like those provided by Hostman.
To begin, we need to update all packages:
sudo apt update
Ubuntu provides the latest version of the Python interpreter by default. Let’s check the installed version using the following command:
python3 --version
Example output:
Python 3.10.12
We’ll set up a virtual environment to ensure that our project has its own dependencies, separate from other projects. First, install the virtualenv package, which allows you to create virtual environments:
sudo apt-get install python3-venv python3-dev
Next, create a folder for your project and navigate into it:
mkdir myapp
cd myapp
Now, create a virtual environment:
python3 -m venv venv
And create a folder for your project:
mkdir app
Your project directory should now contain two items: app
and venv
.
You can verify this using the standard Linux command to list directory contents:
ls
Expected output:
myapp venv
You need to activate the virtual environment so that all subsequent components are installed locally for the project:
source venv/bin/activate
Gunicorn (Green Unicorn) is a Python WSGI HTTP server for UNIX. It is compatible with various web frameworks, fast, easy to implement, and uses minimal server resources.
To install Gunicorn, run the following command:
pip install gunicorn
WSGI (Web Server Gateway Interface) is the standard interface between a Python application running on the server side and the web server itself, such as Nginx. A WSGI server interacts with the application, allowing you to run code when handling requests. Typically, the application is provided as an object named application in a Python module, which is made available to the server.
In the standard wsgi.py
file, there is usually a callable application. For example, let’s create such a file using the nano
text editor:
nano wsgi.py
Add the following simple code to the file:
from aiohttp import web
async def index(request):
return web.Response(text="Welcome home!")
app = web.Application()
app.router.add_get('/', index)
In the code above, we import aiohttp
, a library that provides an asynchronous HTTP client built on top of asyncio
. HTTP requests are a classic example of where asynchronous handling is ideal, as they involve waiting for server responses, during which other code can execute efficiently. This library allows sequential requests to be made without waiting for the first response before sending a new one. It’s common to run aiohttp
servers behind Nginx.
You can launch the server using the following command template:
gunicorn [OPTIONS] [WSGI_APP]
Here, [WSGI_APP]
consists of $(MODULE_NAME):$(VARIABLE_NAME)
and [OPTIONS]
is a set of parameters for configuring Gunicorn.
A simple command would look like this:
gunicorn wsgi:app
To restart Gunicorn, you can use:
sudo systemctl restart gunicorn
systemd
is a system and service manager that allows for strict control over processes, resources, and permissions. We’ll create a socket thatsystemd
will listen to, automatically starting Gunicorn in response to traffic.
First, create the service configuration file:
sudo nano /etc/systemd/system/gunicorn.service
Add the following content to the file:
[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target
[Service]
Type=notify
User=someuser
Group=someuser
RuntimeDirectory=gunicorn
WorkingDirectory=/home/someuser/myapp
ExecStart=/path/to/venv/bin/gunicorn wsgi:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
[Install]
WantedBy=multi-user.target
Make sure to replace /path/to/venv/bin/gunicorn
with the actual path to the Gunicorn executable within your virtual environment. It will likely look something like this: /home/someuser/myapp/venv/bin/gunicorn
.
Next, create the socket configuration file:
sudo nano /etc/systemd/system/gunicorn.socket
Add the following content:
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
[Install]
WantedBy=sockets.target
Enable and start the socket with:
systemctl enable --now gunicorn.socket
Let's review some useful parameters for Gunicorn in Python 3. You can find all possible parameters in the official documentation.
-b BIND
, --bind=BIND
— Specifies the server socket. You can use formats like: $(HOST)
, $(HOST):$(PORT)
.Example:
gunicorn --bind=127.0.0.1:8080 wsgi:app
This command will run your application locally on port 8080.
-w WORKERS
, --workers=WORKERS
— Sets the number of worker processes. Typically, this number should be between 2 to 4 per server core.Example:
gunicorn --workers=2 wsgi:app
-k WORKERCLASS
, --worker-class=WORKERCLASS
— Specifies the type of worker process to run.By default, Gunicorn uses the sync
worker type, which is a simple synchronous worker that handles one request at a time. Other worker types may require additional dependencies.
Asynchronous worker processes are available using Greenlets (via Eventlet or Gevent). Greenlets are a cooperative multitasking implementation for Python. The corresponding parameters are eventlet
and gevent
.
We will use an asynchronous worker type compatible with aiohttp
:
gunicorn wsgi:app --bind localhost:8080 --worker-class aiohttp.GunicornWebWorker
You can enable access logging using the --access-logfile
flag.
Example:
gunicorn wsgi:app --access-logfile access.log
To specify an error log file, use the following command:
gunicorn wsgi:app --error-logfile error.log
You can also set the verbosity level of the error log output using the --log-level
flag. Available log levels in Gunicorn are:
debug
info
warning
error
critical
By default, the info
level is set, which omits debug-level information.
First, install Nginx with the command:
sudo apt install nginx
Let’s check if the Nginx service can connect to the socket created earlier:
sudo -u www-data curl --unix-socket /run/gunicorn.sock http
If successful, Gunicorn will automatically start, and you'll see the HTML code from the server in the terminal.
Nginx configuration involves adding config files for virtual hosts. Each proxy configuration should be stored in the /etc/nginx/sites-available
directory.
To enable each proxy server, create a symbolic link to it in /etc/nginx/sites-enabled
. When Nginx starts, it automatically loads all proxy servers in this directory.
Create a new configuration file:
sudo nano /etc/nginx/sites-available/myconfig.conf
Then create a symbolic link with the command:
sudo ln -s /etc/nginx/sites-available/myconfig.conf /etc/nginx/sites-enabled
Nginx must be restarted after any changes to the configuration file to apply the new settings.
First, check the syntax of the configuration file:
nginx -t
Then reload Nginx:
nginx -s reload
Gunicorn is a robust and versatile WSGI server for deploying Python applications, offering flexibility with various worker types and integration options like Nginx for load balancing and reverse proxying. Its ease of installation and configuration, combined with detailed logging and scaling options, make it an excellent choice for production environments. By utilizing Gunicorn with frameworks like aiohttp
and integrating it with Nginx, you can efficiently serve Python applications with improved performance and resource management.