IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

Run a Python App with Gunicorn and Nginx on Hostperl VPS

By Raman Kumar

Share:

Updated on Aug 10, 2026

Run a Python App with Gunicorn and Nginx on Hostperl VPS

Start with a clean VPS and a working admin login

Run a Python app with Gunicorn and Nginx on a Hostperl VPS starts the same way for most setups: connect, confirm the operating system, create a non-root admin, and install only what the app needs. If you are sizing a fresh machine for this work, a Hostperl VPS gives you enough control for Python, systemd, firewall rules, and TLS without the overhead of a full dedicated server.

On your local computer, open the first SSH session with the reserved documentation IP below. Replace 203.0.113.10 with your real public IP from Hostperl.

ssh root@203.0.113.10

That IP is only an example. Keep the root session open until the new admin login works in a second terminal.

On the VPS as root, check the operating system before you choose package commands.

cat /etc/os-release

You should see whether the server is Ubuntu, Debian, AlmaLinux, or Rocky Linux. The next steps are split so you do not mix apt with dnf.

Create a non-root admin before you install anything

A fresh server should not stay on root for routine work. Create a sudo user named deploy, add your SSH key, and test the account before you close the root session. For a broader account workflow, Hostperl also covers the same pattern in Create a Non-Root Sudo User on Linux VPS and Manage Users and Sudo on Ubuntu, Debian, AlmaLinux.

Ubuntu and Debian

adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

This creates the account, grants sudo, and copies the existing key so you can log in without a password. If you do not already use SSH keys, add one from your local computer with ssh-keygen first, then copy the public key into /home/deploy/.ssh/authorized_keys.

AlmaLinux and Rocky Linux

useradd -m deploy
passwd deploy
usermod -aG wheel deploy
mkdir -p /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

On RHEL-compatible systems, the wheel group controls sudo access. If you copied a key, a password is still useful for recovery, but you can lock it later after login testing.

Open a second terminal on your local computer and test the new account.

ssh deploy@203.0.113.10

Replace the example IP with your server’s real address. Once connected, verify sudo works.

sudo -v
whoami
pwd

You should see deploy for whoami, and sudo should accept your password or key-based authentication.

Update the system, time sync, and install the runtime

Before Gunicorn touches port 8000, update the machine and install the Python packages needed for a service-based deployment. If package updates have caused trouble for you before, Hostperl has a separate guide on fixing package updates without breakage.

Ubuntu and Debian

sudo apt update
sudo apt -y upgrade
sudo apt -y install python3 python3-venv python3-pip nginx
sudo timedatectl set-ntp true
python3 --version
nginx -v

AlmaLinux and Rocky Linux

sudo dnf -y update
sudo dnf -y install python3 python3-pip nginx
sudo timedatectl set-ntp true
python3 --version
nginx -v

At this point, your host should be current, time should be synced, and Nginx should be present. That matters for TLS later, because certificate tools are sensitive to clock drift.

Build the app directory and virtual environment

Use a simple directory layout under /opt/myapp. It keeps the deployment easy to support, which matters when you hand the server to a developer, agency, or client team.

On the VPS as the non-root sudo user:

sudo mkdir -p /opt/myapp
sudo chown -R deploy:deploy /opt/myapp
cd /opt/myapp
python3 -m venv venv
. venv/bin/activate
python -m pip install --upgrade pip
pip install gunicorn flask

This creates an isolated Python environment and installs Gunicorn plus Flask for a simple smoke-tested app. If your application already has a requirements file, replace the package install line with pip install -r requirements.txt.

Create a small application file so you can confirm the full chain works.

cat > /opt/myapp/app.py <<'EOF'
from flask import Flask
app = Flask(__name__)

@app.get('/')
def home():
    return 'Gunicorn and Nginx on a Hostperl VPS is working.\n'
EOF

That file returns one plain text line. It is enough to confirm Gunicorn, Nginx, and the reverse proxy path before you deploy a larger app.

Run Gunicorn under systemd

Use systemd so the app starts after reboot and restarts cleanly if it crashes. This is the same approach customers use for production Python services on managed VPS hosting.

Create a dedicated service account for the process if you do not want to run Gunicorn as your login user. For simplicity in this tutorial, we will use deploy as the owner of the app files and service.

On the VPS as root or with sudo, create the service file.

sudo nano /etc/systemd/system/myapp.service

Paste this full content.

[Unit]
Description=Gunicorn service for myapp
After=network.target

[Service]
User=deploy
Group=deploy
WorkingDirectory=/opt/myapp
Environment="PATH=/opt/myapp/venv/bin"
ExecStart=/opt/myapp/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 app:app
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

Save and exit the editor. On nano, press Ctrl+O, Enter, then Ctrl+X.

Now load the new unit, check the syntax, and start the service.

sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp --no-pager
sudo ss -ltnp | grep 8000

You want to see active (running) in systemd and a listener on 127.0.0.1:8000. If the service fails, the journal usually explains why.

sudo journalctl -u myapp -n 50 --no-pager

Put Nginx in front of the app

Nginx should listen on port 80 and pass requests to Gunicorn on localhost. That is the normal pattern for Python web apps, and it keeps the application port off the public internet. If you want a deeper comparison of web servers before choosing your stack, see Nginx vs Apache for VPS Hosting in 2026.

Remove the default site if your distro ships one, then create a server block.

Ubuntu and Debian

sudo rm -f /etc/nginx/sites-enabled/default
sudo nano /etc/nginx/sites-available/myapp

AlmaLinux and Rocky Linux

sudo nano /etc/nginx/conf.d/myapp.conf

Use this Nginx config. On Debian-based systems, place it in the file you opened above; on RHEL-based systems, use the .conf file under /etc/nginx/conf.d/.

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

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Replace server.example.com with your real hostname once DNS is ready. Save and exit, then test the syntax before reload.

Ubuntu and Debian

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
sudo nginx -t
sudo systemctl reload nginx

AlmaLinux and Rocky Linux

sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx

The syntax test must say test is successful. If it does not, fix the config before reloading.

Open the firewall without exposing the app port

Your firewall should allow SSH and HTTP, but not Gunicorn’s internal port. That way, only Nginx is public.

Ubuntu and Debian with UFW

sudo apt -y install ufw
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose

AlmaLinux and Rocky Linux with firewalld

sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

If you are moving from a hardening baseline, add the new rule before you remove the old one. That avoids lockouts.

Enable HTTPS with Let’s Encrypt

When DNS points to the server, add TLS so browsers trust the site and your redirect from HTTP to HTTPS is handled correctly. For customers managing domains, DNS, and renewal paths, Hostperl’s domain and DNS hosting options help keep records aligned with the server cutover. If your site later needs email authentication, pair this with DMARC, SPF, and DKIM for Better Email Deliverability.

Ubuntu and Debian

sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d server.example.com

AlmaLinux and Rocky Linux

sudo dnf -y install certbot python3-certbot-nginx
sudo certbot --nginx -d server.example.com

Replace server.example.com with the real hostname. When Certbot asks, choose the redirect option so HTTP moves to HTTPS automatically.

Verify renewal works before you leave the server.

sudo certbot renew --dry-run

Check the app from the server and from your browser

Run a local HTTP check first. It isolates Nginx and Gunicorn from DNS and browser cache.

curl -I http://127.0.0.1
curl https://server.example.com

The first command should return headers from Nginx, and the second should show your text response over HTTPS. From your local computer, open the site in a browser and confirm the page loads without certificate warnings.

Then check the services after a reboot-style restart.

sudo systemctl restart myapp nginx
sudo systemctl status myapp nginx --no-pager
sudo reboot

After the server comes back, reconnect and confirm both services start automatically.

sudo systemctl is-enabled myapp nginx
sudo systemctl status myapp nginx --no-pager
sudo ss -ltnp | grep -E '(:80|:443|:8000)'

Troubleshooting the failures you are most likely to hit

Gunicorn exits immediately
Diagnostic:

sudo journalctl -u myapp -n 50 --no-pager

If you see ModuleNotFoundError or a bad working directory, correct the app path in ExecStart or reinstall the Python package in the virtual environment.

Nginx returns 502 Bad Gateway
Diagnostic:

sudo tail -n 50 /var/log/nginx/error.log
sudo ss -ltnp | grep 8000

If Gunicorn is not listening, start the service again. If Nginx points to the wrong port, fix proxy_pass and run nginx -t before reloading.

Certificate issuance fails
Diagnostic:

sudo certbot certificates
sudo journalctl -u certbot -n 50 --no-pager

Usually the DNS record does not point to the server yet, port 80 is blocked, or the hostname in the Certbot command does not match the live domain.

If you want this setup handled on a server that already has room to grow, Hostperl VPS plans are a practical fit for Python apps, reverse proxies, and future TLS renewal work. If your launch needs more consistent traffic headroom, a Hostperl VPS or a higher-capacity dedicated platform can keep the same layout while giving you more CPU and memory.

Our support team can also help with migration timing, firewall checks, and DNS cutover so you do not have to guess where the failure is.

FAQ

Do I need Nginx for Gunicorn?
No, but you should place Nginx in front of Gunicorn for public sites. Nginx handles TLS, buffering, and static file delivery better than Gunicorn alone.

Can I bind Gunicorn directly to port 80?
You can, but you should not. Keep Gunicorn on localhost:8000 and let Nginx own the public port.

What if I use Debian instead of Ubuntu?
The apt commands are nearly the same. The main difference is the package set and the default Nginx site layout.

How many Gunicorn workers should I use?
Start with 2 for a small VPS, then adjust based on CPU and memory. A larger application may need more workers or a larger Hostperl VPS.

What should I check first after a failed deploy?
Check systemctl status myapp, then journalctl -u myapp, then nginx -t, then the Nginx error log. That order usually finds the problem quickly.

Final verification checklist

Before you hand the site to a client or team, confirm these commands all succeed:

sudo systemctl status myapp nginx --no-pager
sudo nginx -t
sudo certbot renew --dry-run
curl -I https://server.example.com
sudo reboot

If those checks pass, your Gunicorn and Nginx on a Hostperl VPS deployment is ready for real traffic, routine support, and future changes without rebuilding the server from scratch.