Skip to content
Back to the blog

Host a Django app on a VPS: Gunicorn, Nginx and production checks

Deploy a Python and Django project on a Linux VPS with a virtual environment, Gunicorn, systemd and Nginx, then check security, static files and backups.

  • Django hosting
  • Python VPS
  • Gunicorn
  • Nginx

If an AI coding tool produced a Django project, the public preview is not the production server. A dynamic Django site needs a Python runtime and an application server; a VPS gives you the operating-system access to install and maintain them. This is a self-managed deployment: you are responsible for patching the OS, configuring the services, securing secrets and arranging backups.

The outline below uses Debian or Ubuntu, Gunicorn for a WSGI project and Nginx in front. It is a template, not a deployment tested against your repository. Replace djangoapp, myproject, the domain and paths with the values from your project. If it is ASGI-only, uses workers or has a different static/media setup, adapt the server accordingly.

1. Check the project before choosing the server

In the repository, locate manage.py, requirements.txt or pyproject.toml, and the WSGI/ASGI module. Run the app locally and note the Python and database versions it requires. Inspect any AI-generated settings for DEBUG, SECRET_KEY, ALLOWED_HOSTS, CSRF trusted origins, database URLs, API keys, uploads and background jobs. Do not push .env or production secrets to Git.

Django’s runserver is for development, not production. Django recommends a production WSGI or ASGI server, and its current checklist includes manage.py check --deploy. A static-only project may fit a static hosting plan; this VPS flow is for an app that needs Django running for requests.

2. Prepare a Linux VPS and a restricted app user

Choose a Hostbrid VPS with a Debian or Ubuntu image that supports your project’s Python version. Connect over SSH, apply system updates and allow only the network ports you actually need. Install the operating-system packages and create a separate service account rather than running Django as root:

sudo apt update
sudo apt install git python3-venv nginx
sudo adduser --system --group --home /srv/djangoapp djangoapp

Clone your repository into /srv/djangoapp/current as that user. For a private repository, use a read-only deploy key and keep its private key out of the project. Create a virtual environment and install the versions recorded by the project:

sudo -u djangoapp python3 -m venv /srv/djangoapp/venv
sudo -u djangoapp /srv/djangoapp/venv/bin/pip install -r /srv/djangoapp/current/requirements.txt

Make sure Gunicorn is a declared, pinned project dependency. Do not install packages globally for the app.

3. Set production configuration and prepare the app

Store production environment variables in a root-managed file readable by the service account, not in Git. Use the variable names your settings actually read. Generate a unique secret key; turn DEBUG off; set the exact hostnames in ALLOWED_HOSTS; configure CSRF origins and HTTPS behavior; and provide database credentials without committing them.

Run the management commands below with the same production settings module and environment variables the service will use. Otherwise a deployment check may inspect development defaults instead of the live configuration.

Before changing a live database, take a backup and confirm how you would restore it. Then activate the project environment, run migrations, collect static files and run Django’s deployment checks with the production settings:

cd /srv/djangoapp/current
sudo -u djangoapp /srv/djangoapp/venv/bin/python manage.py migrate
sudo -u djangoapp /srv/djangoapp/venv/bin/python manage.py collectstatic --noinput
sudo -u djangoapp /srv/djangoapp/venv/bin/python manage.py check --deploy

collectstatic needs a configured STATIC_ROOT. Keep user-uploaded media separate from static assets; plan permissions and backups, and treat uploads as untrusted files.

4. Run Gunicorn as a systemd service

First test the WSGI entry point from the project directory. Replace myproject.wsgi:application with the module that contains your project’s WSGI application object:

cd /srv/djangoapp/current
sudo -u djangoapp /srv/djangoapp/venv/bin/gunicorn --bind 127.0.0.1:8000 myproject.wsgi:application

Once that works, run it under systemd so the process starts at boot and can restart after a failure. A minimal unit uses the same project-specific WSGI path and loads the protected environment file:

[Unit]
Description=Django application
After=network.target

[Service]
User=djangoapp
Group=djangoapp
WorkingDirectory=/srv/djangoapp/current
EnvironmentFile=/etc/djangoapp.env
ExecStart=/srv/djangoapp/venv/bin/gunicorn --bind 127.0.0.1:8000 myproject.wsgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

Save it as /etc/systemd/system/djangoapp.service, then run sudo systemctl daemon-reload, sudo systemctl enable --now djangoapp, and check sudo systemctl status djangoapp plus journalctl -u djangoapp. Tune workers and memory only after checking the VPS resources and application load.

5. Put Nginx and HTTPS in front

For a project whose STATIC_URL is /static/ and STATIC_ROOT is /srv/djangoapp/current/staticfiles, a starting Nginx site looks like this. Change the hostname and paths to match your project:

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

    location /static/ {
        alias /srv/djangoapp/current/staticfiles/;
    }

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

Save the site in the distribution’s Nginx configuration directory, enable it, then run sudo nginx -t and reload Nginx. Keep Gunicorn bound to loopback so it is not exposed directly to the internet. Point the domain to the VPS address, allow ports 80/443, issue a certificate using the method supported by your distribution, and redirect visitors to HTTPS before accepting logins or other sensitive data. This HTTP block is a reverse-proxy starting point, not a finished HTTPS configuration.

If Django sits behind a proxy, configure secure-proxy settings only for the trusted proxy path you control. Test the homepage, admin login, a deep URL, static files, forms and any API integrations. Check service logs after a restart and confirm that secrets do not appear in the browser or logs.

Before opening the site

Run the Django deployment checklist against production settings, verify database and media backups, and test a restore. Also review Django with Gunicorn and Nginx’s proxy module. These checks do not configure or monitor the VPS for you.

This route suits a Python app that needs a process you control. If your project uses Flask, see the Flask guide for Gunicorn and Nginx. Hostbrid supplies the VPS and root access; you install and operate Django, the database, Nginx, HTTPS, updates and backups. Compare the VPS options and current prices with the app’s memory, CPU and storage needs before ordering.

Manage your sites, servers and email.

Forgot your password?

Free, and it takes a minute.

At least 8 characters. Not all digits, not a common one.

We'll email you to confirm the account.