Setting up Gunicorn as a Python server

by Lance Gold

This python server should work well behind NginX

Return to index
Some references used for this page:

https://gunicorn.org/quickstart/

Google A.I.
debian gunicorn what is --bind to a specific unix socket file

debian use gunicorn with nginx server

Do I have the dependencies installed

debian what folder for a python web app

debian what is a python venv

debian use gunicorn with nginx server

debian how to install python3-venv

debian nginx reverse proxy for web server and gunicorn app server

Open up two firewall ports

$ sudo vi /etc/nftables.conf

After with added tcp dport { 8000, 8080 } accept # Quincorn with Python


$ more /etc/nftables.conf
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
        chain input {
                type filter hook input priority filter;
                policy drop;

                ct state established, related accept
                iifname "lo" accept
                icmp type echo-request accept

                tcp dport 22 accept     #ssh
                tcp dport { 80, 443 } accept    #http, https
                tcp dport { 3000, 3306 } accept #mySQL, mariaDB
                tcp dport { 3000, 3306 } accept #mySQL, mariaDB
                tcp dport { 8000, 8080 } accept #Qunicorn with Python
                log prefix "Server Block: " flags all
        }
        chain forward {
                type filter hook forward priority filter;
        }
        chain output {
                type filter hook output priority filter;
        }
}
you@de:/etc/systemd/system$

To apply changes without a full service restart:

you@de:/etc/systemd/system$ sudo nft -f /etc/nftables.conf
you@de:/etc/systemd/system$

List Active Rules:

you@de:/etc/systemd/system$ sudo nft list ruleset
table inet filter {
        chain input {
                type filter hook input priority filter; policy drop;
                ct state established,related accept
                iifname "lo" accept
                icmp type echo-request accept
                tcp dport 22 accept
                tcp dport { 80, 443 } accept
                tcp dport { 3000, 3306 } accept
                tcp dport { 8000, 8080 } accept
                log prefix "Server Block: " flags all
        }

        chain forward {
                type filter hook forward priority filter; policy accept;
        }

        chain output {
                type filter hook output priority filter; policy accept;
        }
}
$

Check if Python is installed


$ python3 --version
Python 3.13.5

Check if the package manager pip is installed


$ python3 -m pip --version
/usr/bin/python3: No module named pip

Choosing a debian directory to place the python server project
Recommended Directories

Following the Linux Filesystem Hierarchy Standard (FHS) and common practices for web applications on Debian:

•/var/www/myapp/: This is a widely used and recommended location for the application's source code, especially when deploying with web servers like Apache or Nginx.

•/srv/myapp/: The FHS defines /srv as the location for site-specific data that is served by the system, so this is also an appropriate choice.

•/opt/myapp/: This directory is designated for optional application software that is not part of the standard system installation. It's a good choice for self-contained, third-party applications installed without a package manager (like apt).

Why use a Python virtual environment?
•Isolation of Dependencies: Different projects often require different versions of the same library. A venv allows you to install specific package versions for one project (e.g., Application A needs version 1.0 of a module, and Application B needs version 2.0) without creating conflicts.

•Protection of System Python: On Debian and other Linux systems, many critical system tools rely on the system Python installation. Using pip to install, upgrade, or remove packages globally can accidentally break system components. A venv ensures that third-party packages are installed locally within the environment's directory, leaving the system Python untouched.

How does it work?

When a virtual environment is created, it makes a folder (commonly named .venv or venv) that contains:

•A copy or symlink to the base Python executable. Its own pip installer.

•Its own site-packages directory where all project-specific packages are installed.

•Activation scripts for various shells (Bash, Csh, PowerShell, etc.).

•A configuration file (pyvenv.cfg) that links back to the base Python installation used to create it.

When you "activate" a virtual environment using its script (e.g., source venv/bin/activate on Linux), your shell's prompt changes (usually showing the environment's name in parentheses) and modifies your system's PATH environment variable so that running python or pip commands uses the executables within the environment's directory instead of the system ones.

1. Set up Your Python Application
•Install Dependencies: Install Python and nginx using apt.

sudo apt update
sudo apt install python3 python3-pip python3-venv nginx

$ sudo apt install python3-pip
Installing:
  python3-pip

Installing dependencies:
  libexpat1-dev    libjs-underscore  libpython3.13-dev  python3.13-dev
  libjs-jquery     libpython3-dev    python3-dev        zlib1g-dev
  libjs-sphinxdoc  libpython3.13     python3-wheel

Suggested packages:
  python3-setuptools

Summary:
  Upgrading: 0, Installing: 12, Removing: 0, Not Upgrading: 8
  Download size: 11.0 MB
  Space needed: 49.7 MB / 23.0 GB available

Continue? [Y/n]

$ sudo apt install python3-venv
Installing:
  python3-venv

Installing dependencies:
  python3-pip-whl  python3-setuptools-whl  python3.13-venv

Summary:
  Upgrading: 0, Installing: 4, Removing: 0, Not Upgrading: 8
  Download size: 2,782 kB
  Space needed: 2,957 kB / 22.9 GB available

Continue? [Y/n]

•Create a Virtual Environment: Create a directory for your project and set up a virtual environment.


mkdir ~/myproject
cd ~/myproject
python3 -m venv venv
source venv/bin/activate

$ ls -l /opt/
total 4
drwxrwxr-x 2 root ndev 4096 Jan 31 15:11 nginx_server_project
$ sudo mkdir /opt/python_server_project
$ ls -l /opt/
total 8
drwxrwxr-x 2 root ndev 4096 Jan 31 15:11 nginx_server_project
drwxr-xr-x 2 root root 4096 Feb  2 17:36 python_server_project
$ sudo chgrp ndev /opt/python_server_project
$

Create the subdirectory to hold the virtual environment


you@de:/opt/python_server_project$ sudo python3 -m venv web_env

This creates a directory named web_env containing an isolated Python environment.


you@de:/opt/python_server_project$ ls web_env
bin  include  lib  lib64  pyvenv.cfg
you@de:/opt/python_server_project$ ls web_env/bin
activate      activate.fish  pip   pip3.13  python3
activate.csh  Activate.ps1   pip3  python   python3.13
you@de:/opt/python_server_project$

Activate the virtual environment:


you@de:/opt/python_server_project$ source web_env/bin/activate
(web_env) you@de:/opt/python_server_project$

Deactivate the environment:

When you are finished working in the environment, simply type deactivate in the terminal.

•Install Gunicorn: Install Gunicorn within your virtual environment.


pip install gunicorn

(web_env) you@de:/opt/python_server_project$ sudo pip install gunicorn
[sudo] password for x:
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.

    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.

    See /usr/share/doc/python3.13/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
(web_env) you@de:/opt/python_server_project$

(web_env) you@de:/opt/python_server_project$ cd web_env
(web_env) you@de:/opt/python_server_project/web_env$

(web_env) you@de:/opt/python_server_project/web_env$ sudo bin/pip install gunicorn
Collecting gunicorn
  Downloading gunicorn-25.0.1-py3-none-any.whl.metadata (4.7 kB)
Collecting packaging (from gunicorn)
  Downloading packaging-26.0-py3-none-any.whl.metadata (3.3 kB)
Downloading gunicorn-25.0.1-py3-none-any.whl (169 kB)
Downloading packaging-26.0-py3-none-any.whl (74 kB)
Installing collected packages: packaging, gunicorn
Successfully installed gunicorn-25.0.1 packaging-26.0
(web_env) you@de:/opt/python_server_project/web_env$

•Create Your Application: Create a simple Python application (e.g., a Flask or Django app) in your project directory. For this example, we assume a simple Flask app named app.py with an application instance called app.


(web_env) you@de:/opt/python_server_project/web_env$ cd ..
(web_env) you@de:/opt/python_server_project$
(web_env) you@de:/opt/python_server_project$ sudo touch app.py
[sudo] password for x:
(web_env) you@de:/opt/python_server_project$ sudo vi app.py

Install the flask module from in the virtual environment:


(web_env) you@de:/opt/python_server_project$ sudo web_env/bin/pip install flask

Here is an app.py using the flask module:


(web_env) you@de:/opt/python_server_project$ more app.py

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello, world! from app.py\n"


(web_env) you@de:/opt/python_server_project$

2. Configure and Run Gunicorn
•Test Gunicorn: Run your application with Gunicorn to ensure it works, binding to a local port (e.g., 8000). bash

(web_env) you@de:/opt/python_server_project$ gunicorn --bind 0.0.0.0:8000 app:app
[2026-02-02 20:13:56 -0800] [2001424] [INFO] Starting gunicorn 25.0.1
[2026-02-02 20:13:56 -0800] [2001424] [INFO] Listening at: http://0.0.0.0:8000 (2001424)
[2026-02-02 20:13:56 -0800] [2001424] [INFO] Using worker: sync
[2026-02-02 20:13:56 -0800] [2001425] [INFO] Booting worker with pid: 2001425

Do I have curl?

you@de:~$ curl --version
-bash: curl: command not found
you@de:~$

Install curl software to test local access to app through ports


you@de:~$ sudo apt update
you@de:~$ sudo apt install curl

Using a new terminal, test the app.py on the local port.


@c7:~$ curl 127.0.0.1:8000
Hello, world! from app.py
you@de:~$

Using the terminal running Gunicorn, press <ctrl-c> to stop the server


you@de:~$ cd /opt/python_server_project
you@de:/opt/python_server_project$ /

Test with the default 127.0.0.1:8000


you@de:/opt/python_server_project$ /opt/python_server_project/web_env/bin/gunicorn app:app
[2026-02-05 09:12:56 -0800] [2094239] [INFO] Starting gunicorn 25.0.1
[2026-02-05 09:12:56 -0800] [2094239] [INFO] Listening at: http://127.0.0.1:8000 (2094239)
[2026-02-05 09:12:56 -0800] [2094239] [INFO] Using worker: sync
[2026-02-05 09:12:56 -0800] [2094240] [INFO] Booting worker with pid: 2094240

From the other terminal test the server


you@de:~$ curl 127.0.0.1:8000
Hello, world! from app.py
you@de:~$

How to create a gunicorn sock

Test with the security of binding Gunicorn to a specific file.

The --bind option in Gunicorn specifies the socket to bind to using the syntax --bind unix:/path/to/socket/file. This approach allows Gunicorn to communicate through a local socket file instead of a TCP port, which is a common setup for connecting a reverse proxy like Nginx or Apache to the Gunicorn process.

A .sock file is a special file used for inter-process communication via a Unix domain socket. You do not create a socket file with standard commands like touch or mknod in the same way you would a regular file or directory; instead, the file is automatically created by a running program (server process) when it binds to a specific filesystem path.

Key Considerations

•Permissions: The Unix socket file must have the correct permissions so that the web server (e.g., Nginx) and the Gunicorn worker processes can read from and write to it.

•Performance: Using a Unix socket generally offers lower latency compared to using a TCP loopback interface because the communication occurs entirely within the kernel, avoiding some network overhead [1].

•Reverse Proxy: You will configure your reverse proxy (e.g., Nginx) to listen on a specific port and proxy requests to this local Unix socket file.

Start Gunicorn (with sudo)


you@de:/opt/python_server_project$ sudo /opt/python_server_project/web_env/bin/gunicorn --bind unix:py_app.sock -m 007 app:app

Here is a temporarily created socket named py_app.sock visible while --bind unix:py_app.sock runs:


you@de:~$ ls -l /opt/python_server_project/
total 12
-rw-rw-r-- 1 my-servicer my-servicer  117 Feb  2 19:09 app.py
srwxrwxrwx 1 root        root           0 Feb  5 13:20 py_app.sock
drwxrwxr-x 2 my-servicer my-servicer 4096 Feb  5 00:12 __pycache__
drwxrwxr-x 5 my-servicer my-servicer 4096 Feb  2 17:43 web_env
you@de:~$

•Create a Systemd Service: To manage Gunicorn as a service, create a systemd service file (e.g., /etc/systemd/system/myproject.service).

you@de:~$ cd /etc/systemd/system/

you@de:/etc/systemd/system$ sudo touch python-server-project.service
you@de:/etc/systemd/system$

Here is the contents of the file:


you@de:/etc/systemd/system$ more python-server-project.service
[Unit]
Description=Gunicorn instance to serve python_server_project
After=network.target

[Service]
User=my-servicer
Group=www-data
WorkingDirectory=/opt/python_server_project
ExecStart=/opt/python_server_project/web_env/bin/gunicorn --workers 3 --bind un
ix:hello-py.sock -m 007 wsgi:app

[Install]
WantedBy=multi-user.target

my-servicer is a name picked for a new special user account, an account with no home directory and no login to prevent a security break.

www-data is a name given for the http team of developers

...web_env/bin/gunicorn executes the gunicorn installed inside of the web_env virtual directories

--workers 3 creates 3 gunicorn server sockets listening for data. A rule is 2 * (number of processors) + 1

hello-py.sock will be created in the Working directory when systemctl starts the server (and deleted when the server is stopped). The created file will have the permissions rwx for user, group, but not public wsgi is the filename of the wsgi.py file in the working directory. Inside wsgi.py is a call to the hello.py file. :app is the name of the app variable (app object) in the hello.py file.

•Enable and Start the Service: Reload the systemd daemon, enable, and start your Gunicorn service.
sudo systemctl daemon-reload

sudo systemctl enable myproject

sudo systemctl start myproject


you@de:/etc/systemd/system$ sudo systemctl daemon-reload
[sudo] password for x:
you@de:/etc/systemd/system$ sudo systemctl enable python-server-project
Created symlink '/etc/systemd/system/multi-user.target.wants/python-server-project.service' → '/etc/systemd/system/python-server-project.service'.
you@de:/etc/systemd/system$ sudo start python-server-project
sudo: start: command not found
you@de:/etc/systemd/system$ sudo systemctl start python-server-project
you@de:/etc/systemd/system$

3. Configure Nginx as a Reverse Proxy

•Create an Nginx to Gunicorn Configuration File: Create a new Nginx configuration file in /etc/nginx/sites-available/myproject.

Here is a basic nginx.config file:


server {
    listen 80;
    server_name your_domain.com www.your_domain.com; # Replace with your domain or IP

    location / {
        include proxy_params;
        proxy_pass http://unix:/home/youruser/myproject/myproject.sock;
    }

    # Optional: location to serve static files directly if applicable
    # location /static/ {
    #     root /home/youruser/myproject/static;
    # }
}

Add Configuration to the nginx.config file: Add the server block configuration. This example assumes routing traffic to the Gunicorn app via a /app/ path AND other traffic to a general web server.


server {
    listen 80;
    server_name example.com www.example.com;

    # Configuration for the general web server (e.g., static files or Apache)
    location / {
        # Option 1: Serve static files from a root directory
        root /var/www/html;
        index index.html index.htm;
        try_files $uri $uri/ =404;

        # Option 2: Proxy to another web server (e.g., Apache running on 8080)
        # proxy_pass http://127.0.0.1:8080;
        # proxy_set_header Host $host;
        # proxy_set_header X-Real-IP $remote_addr;
        # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Configuration for the Gunicorn application server
    location /app/ {
        proxy_pass http://127.0.0.1; # Or use a Unix socket: http://unix:/tmp/myproject.sock:/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        # Add other necessary headers for Gunicorn
    }
}

Note: The proxy_pass directive for Gunicorn should point to the address Gunicorn is listening on. Ensure Gunicorn is configured to listen on the specified port (0.0.0.0:8000 for all interfaces or 127.0.0.1:8000 for local communication).

Create and edit the file for python apps py-app:

•Enable the Site: Create a symbolic link from sites-available/myproject to sites-enabled/myproject.


sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

•Test and Restart Nginx: Test the Nginx configuration for syntax errors and restart the Nginx service.


sudo nginx -t
sudo systemctl restart nginx

After these steps, Nginx will receive requests and forward dynamic content requests to Gunicorn via the Unix socket, while optionally serving static files itself. You can access your application by visiting your server's domain name or IP address in a web browser.