Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To install Django on a Linux VPS for production, you need more than pip install django. Build four layers: a secured Linux host, an isolated Python environment with your pinned application dependencies, a production WSGI or ASGI server, and Nginx as the public-facing web server. Add a production database, systemd supervision, DNS, HTTPS, backups, and deployment checks before exposing the site to the internet.

What a production Django VPS actually includes

A VPS is a virtual Linux server with its own CPU, memory, storage, network address, and operating-system instance. Django runs as an application inside that server; it is not itself a complete internet-facing web server.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A maintainable single-VPS deployment normally contains these layers:

  • Linux and SSH: the operating system, firewall, updates, and a non-root deployment account.
  • Python environment: a virtual environment containing Django and the exact versions required by the project.
  • Application server: Gunicorn for a conventional synchronous WSGI application, or an ASGI server when the project needs asynchronous request handling or long-lived connections.
  • Front-end web server: Nginx terminates TLS, serves static or media files where appropriate, validates hosts, and proxies dynamic requests to the application server.
  • Data and operations: PostgreSQL or another production database, systemd, logs, DNS, certificates, backups, and a rollback procedure.

Django’s deployment guidance describes the development server as unsuitable for production. The runserver command is for development and testing; it should not be the process receiving public traffic.

Check compatibility before creating the VPS

Supported Python and Django versions change. Check the Django release supported by your project and the Python version required by every dependency before selecting an operating-system image. The currently published Gunicorn installation guidance states that Gunicorn requires Python 3.12 or newer, so a project using Gunicorn must be tested against a compatible interpreter before provisioning.

Distribution package names and paths differ between Ubuntu, Debian, Fedora, and other Linux distributions. The commands below use Debian/Ubuntu-style names and paths. Adapt package names, firewall commands, service locations, and database-driver packages to your distribution.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Have these values ready

  • VPS public IP address and a DNS name such as example.com.
  • An SSH key and a non-root deployment username.
  • The project’s Git repository or release artifact.
  • The Python version supported by the project.
  • A dependency lock file or pinned requirements.txt.
  • Database credentials and the intended database host.
  • The project’s WSGI or ASGI module, commonly project.wsgi:application or project.asgi:application.

1. Provision the VPS and connect over SSH

Choose a supported Linux image, create or import an SSH key, and create a regular user for deployment. Do not perform routine application work as root. Use the provider’s console only for initial recovery or account setup.

  1. Record the server address and verify that the DNS name you will use is under your control.
  2. From your local computer, connect with the deployment account: ssh deploy@example.com. Replace the name and host with your own values.
  3. Confirm the operating system and Python versions: cat /etc/os-release and python3 --version.
  4. Give the deployment account only the administrative access it needs, normally through a controlled sudo policy.

After SSH works, configure the host firewall to allow SSH and, later, HTTP and HTTPS. Keep the SSH port reachable from your administration network; do not lock yourself out before testing a second session.

2. Update Linux and install build prerequisites

Start with current security updates. On Debian or Ubuntu, a typical base installation is:

sudo apt update
sudo apt upgrade -y
sudo apt install -y python3 python3-venv python3-pip git build-essential nginx

Install additional development headers or database client libraries required by your project. For example, a PostgreSQL adapter may require PostgreSQL client development packages; the exact package depends on the adapter and distribution. Read the project’s installation instructions rather than adding packages speculatively.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Do not assume the distribution’s Django package is the version your application needs. The virtual environment should receive the version selected by the project’s lock or requirements file.

3. Put the project in a controlled directory

Use a predictable directory such as /srv/myproject. Keep the application source separate from uploaded media, collected static files, logs, and secrets.

sudo mkdir -p /srv/myproject
sudo chown -R deploy:deploy /srv/myproject
cd /srv/myproject
git clone YOUR_REPOSITORY_URL app
cd app

Replace YOUR_REPOSITORY_URL with the repository address and substitute the branch or tag used for the release. If the source is copied as an archive instead, verify its checksum and ownership before installing it.

Create and activate the virtual environment

cd /srv/myproject/app
python3 -m venv /srv/myproject/venv
. /srv/myproject/venv/bin/activate
python -m pip install --upgrade pip

Install the project’s pinned dependencies, for example:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
python -m pip install -r requirements.txt

Install Gunicorn inside this environment when the project uses WSGI. Gunicorn’s installation guidance specifically recommends installing it in a virtual environment so application dependencies remain isolated from the operating system. If the project uses ASGI, install the selected ASGI server and its pinned version instead.

Every command in later steps should use the same interpreter. A reliable pattern is /srv/myproject/venv/bin/python and /srv/myproject/venv/bin/gunicorn, which avoids accidentally invoking a system-wide package.

4. Configure production settings securely

Do not commit production secrets to Git. Supply them through environment variables or a root-readable environment file outside the repository. At minimum, production settings need a large random SECRET_KEY, DEBUG = False, and an accurate ALLOWED_HOSTS list.

Django’s deployment checklist warns that “The internet is a hostile environment.” Treat the secret key as a credential: restrict file permissions, rotate it through a planned process, and never paste it into public issue trackers or logs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example environment file

sudo install -o root -g www-data -m 640 /dev/null /etc/myproject.env
sudoedit /etc/myproject.env
DJANGO_SETTINGS_MODULE=project.settings.production
SECRET_KEY=replace-with-a-long-random-value
DEBUG=False
ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=replace-with-your-database-connection

Your settings code must actually read these variables and convert booleans, lists, and connection values to the types it expects. Do not assume that writing DEBUG=False in a file automatically changes Django unless the settings module loads that file.

Set trusted origins, secure cookie options, CSRF configuration, email credentials, storage credentials, and allowed proxy settings according to the project. Ensure the public hostnames in ALLOWED_HOSTS exactly match the names users will send. With DEBUG = False, Django requires a suitable value; leaving it empty causes host validation failures.

5. Configure the production database and release files

A development SQLite file may be adequate for local work but should not be treated as the default production plan for a busy or multi-process application. PostgreSQL is a common production choice. You can run it on the VPS or use a managed database.

Choice Advantages Responsibilities and trade-offs
PostgreSQL on the VPS Local network latency and one-server simplicity. You must patch the database, monitor storage, secure access, test backups, and recover it when the VPS fails.
Managed PostgreSQL Provider-managed maintenance and backup features, with database access separated from the web host. Recurring cost, network configuration, provider-specific recovery procedures, and possible latency between the VPS and database.

Whichever option you choose, create a least-privilege application role, restrict network access, and test restoring a backup. Then apply schema migrations from the activated environment:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cd /srv/myproject/app
/srv/myproject/venv/bin/python manage.py migrate --settings=project.settings.production

Only run migrations against the intended production database. Confirm the connection string and settings before executing the command.

Create an administrator when required

/srv/myproject/venv/bin/python manage.py createsuperuser --settings=project.settings.production

This command is interactive. Use a strong, unique password and protect the administration URL with the application’s normal authentication and HTTPS.

Collect static files

/srv/myproject/venv/bin/python manage.py collectstatic --noinput --settings=project.settings.production

Set STATIC_ROOT to a directory Nginx can read, and configure MEDIA_ROOT separately for user uploads. Static files are release artifacts; media files are user data and need independent backup and access rules. If the project uses remote or manifest-based storage, follow that backend’s configuration instead of exposing arbitrary directories.

6. Choose WSGI or ASGI deliberately

WSGI is the conventional synchronous interface and works with Gunicorn. ASGI is the asynchronous-friendly standard supported by Django for async request handling and features such as long-lived connections. Select the interface that matches the application, not merely the server most often mentioned in tutorials.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Consideration WSGI with Gunicorn ASGI deployment
Workload Synchronous views and ordinary HTTP requests. Async views, WebSockets, streaming, or other long-lived connections.
Application entry point project.wsgi:application project.asgi:application
Operations Simple, widely understood process model. Requires an ASGI-capable server and careful validation of async-compatible dependencies.
Proxying Nginx forwards ordinary HTTP requests. Nginx must preserve the headers and upgrade behavior required by the protocol used.

Do not move a synchronous project to ASGI expecting automatic speed improvements. Confirm that the application and its dependencies support the execution model you select.

7. Test the application server locally

Before adding Nginx, run the production server on loopback or a Unix socket and test it from the VPS. For a WSGI project using Gunicorn:

cd /srv/myproject/app
/srv/myproject/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 project.wsgi:application

Replace project with the package containing wsgi.py. The worker count is an operational starting point, not a universal performance prescription; size it against available memory and workload, then measure.

From another SSH session, request the local endpoint with curl -I http://127.0.0.1:8000/. Stop the foreground process after confirming that Django loads. If it fails, read the traceback, verify the settings module, confirm that the virtual environment contains every dependency, and check database connectivity.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

8. Create a systemd service

A foreground Gunicorn command ends when the SSH session closes. systemd starts the service at boot, restarts it after failure, and records its standard output in the journal.

Create a service file such as /etc/systemd/system/myproject.service:

[Unit]
Description=Gunicorn for myproject
After=network.target

[Service]
User=deploy
Group=www-data
WorkingDirectory=/srv/myproject/app
EnvironmentFile=/etc/myproject.env
ExecStart=/srv/myproject/venv/bin/gunicorn --workers 3 --bind unix:/run/myproject.sock project.wsgi:application
Restart=on-failure

[Install]
WantedBy=multi-user.target

Adjust the user, paths, worker count, entry point, and environment-file location. The service account needs read access to the source and write access only where the application genuinely requires it. A Unix socket keeps the application server off the public network and gives Nginx a simple local endpoint.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Reload unit files: sudo systemctl daemon-reload.
  2. Enable startup and start the service: sudo systemctl enable --now myproject.
  3. Check status: sudo systemctl status myproject.
  4. Read recent logs: sudo journalctl -u myproject -n 100 --no-pager.

If the service fails, systemd will report an exit status, but the application traceback is usually in the journal. Correct the underlying path, permission, import, or configuration error rather than repeatedly restarting it.

Unix socket versus loopback binding

A Unix socket is local-only and avoids a TCP port for the proxy connection. A loopback address such as 127.0.0.1:8000 is easy to inspect with ordinary network tools and can simplify some container or monitoring setups. Both keep the application off the public interface when Nginx is the only externally exposed service. Whichever you choose, make the socket path, ownership, and Nginx configuration agree.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

9. Put Nginx in front of Django

Nginx should accept public HTTP and HTTPS traffic, reject unknown hosts, serve collected static files, and forward dynamic requests to the local application server. Create a site configuration such as /etc/nginx/sites-available/myproject:

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

    location /static/ {
        alias /srv/myproject/static/;
    }

    location /media/ {
        alias /srv/myproject/media/;
    }

    location / {
        proxy_pass http://unix:/run/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;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Use the project’s actual static and media directories. Verify that the Nginx worker can read them, and do not expose the whole project directory. Some Nginx versions require a socket proxy syntax with a trailing colon or a separate upstream block; validate the syntax on the installed version rather than copying an incompatible form.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Enable the site, for example with sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled/myproject.
  2. Check the configuration: sudo nginx -t.
  3. Reload only after the test succeeds: sudo systemctl reload nginx.

Configure a default server that rejects unknown hostnames instead of accidentally serving your application for every address on the VPS. Django’s deployment checklist also recommends host validation in the web server.

DNS and HTTPS

Create an A or AAAA record for the chosen hostname pointing to the VPS address. Wait for the record to resolve, then obtain a certificate from a trusted certificate authority using your distribution’s supported ACME tooling. Configure Nginx to listen on 443, reference the certificate and private key, and redirect port 80 to HTTPS.

After TLS is active, enable Django’s secure-cookie and proxy-aware settings appropriate to your configuration. Test the redirect, certificate chain, login flow, CSRF-protected forms, static files, uploads, and any WebSocket or streaming endpoint. Never send production credentials over an unencrypted HTTP connection.

10. Run deployment checks before opening the site

Use the production settings explicitly:

/srv/myproject/venv/bin/python manage.py check --deploy --settings=project.settings.production

Django recommends this check before release. Treat every warning as a decision to investigate, not as harmless output. Then exercise the application through its public HTTPS hostname.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Open the home page and a representative authenticated page.
  • Log in and log out; verify session cookies are secure.
  • Submit a CSRF-protected form.
  • Load CSS, JavaScript, images, and user-uploaded media.
  • Confirm database reads and writes.
  • Inspect Nginx access/error logs and the systemd journal.
  • Verify that an invalid host is rejected.
  • Confirm that the certificate is valid and HTTP redirects to HTTPS.

Keep a release record containing the deployed commit, migration result, configuration version, and rollback target. A deployment is not complete until you know how to return to the previous working release.

Security and maintenance checklist

  • Keep Linux, Python dependencies, Django, Nginx, and the database patched within their supported versions.
  • Use SSH keys, disable password authentication when practical, and restrict administrative access.
  • Keep SECRET_KEY, database passwords, API keys, and certificate private keys out of source control.
  • Keep DEBUG = False in production and configure ALLOWED_HOSTS precisely.
  • Expose only required network ports; the application server and database should not be publicly reachable unless a documented design requires it.
  • Separate static assets from media uploads and back up media and database data independently.
  • Monitor disk space, memory, worker restarts, certificate expiry, and failed login or error spikes.
  • Test restoration, not merely backup creation. Record where backups live and who can restore them.
  • Use a documented rollback: identify the previous code revision, reverse or safely manage migrations, restore compatible environment values, and restart services in a controlled order.

Troubleshooting common failures

“502 Bad Gateway” from Nginx

Check sudo systemctl status myproject and sudo journalctl -u myproject. Confirm that the service is running, that the socket exists at the exact path in the Nginx configuration, and that Nginx has permission to connect to it. If using loopback instead, verify that the port and address match.

Django reports “DisallowedHost”

Add the exact public hostname to ALLOWED_HOSTS, reload the application service, and ensure Nginx passes the original Host header. Do not solve this by allowing every host.

Static files return 404

Run collectstatic with production settings, verify STATIC_ROOT, compare it with Nginx’s alias, and check directory permissions. A successful application response does not prove that Nginx is serving the correct static directory.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Database connection errors during migration

Check the environment file loaded by systemd and by your shell, confirm the database hostname, port, role, password, SSL requirement, and firewall rules, and test connectivity from the VPS. Ensure the migration command uses the same settings module as the service.

Changes are not visible after deployment

Confirm that the intended Git revision is installed, restart or reload the application service when code or environment values changed, rerun static collection when assets changed, and inspect logs for import or migration errors. Browser and proxy caches may also retain old assets; use versioned static filenames or the project’s cache-busting configuration.

When a single VPS is no longer enough

A single VPS is a useful starting architecture, but it combines application, proxy, and often database failure domains. As traffic or availability requirements grow, separate the database, add a second application instance behind a load balancer, move static and media storage to durable object storage, and centralize logs and monitoring. The same principles remain: pinned dependencies, explicit production settings, supervised processes, HTTPS, tested backups, and a reversible release process.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.