---
title: "Hospedar Django na VPS com Gunicorn, Nginx e PostgreSQL"
description: "Coloque um projeto Django em produção na VPS Ubuntu: venv, settings seguros, Gunicorn como serviço systemd, Nginx com arquivos estáticos, HTTPS e PostgreSQL."
url: "https://streethosting.com.br/guias/vps/hospedar-django-vps-gunicorn-nginx"
category: "vps"
slug: "hospedar-django-vps-gunicorn-nginx"
datePublished: "2026-11-13"
dateModified: "2026-11-13"
author: "Equipe StreetHosting"
difficulty: "intermediario"
language: "pt-BR"
keywords:
  - "hospedar django vps"
  - "django gunicorn nginx ubuntu"
  - "django em produção"
  - "gunicorn systemd"
  - "django collectstatic nginx"
  - "django postgresql vps"
---

# Como hospedar um projeto Django na VPS com Gunicorn, Nginx e PostgreSQL

O runserver não serve para produção. Veja o caminho completo na VPS: ambiente virtual, settings de produção, Gunicorn sob o systemd, Nginx servindo o /static/ e HTTPS com certbot.

> **Resposta rápida**
>
> Em produção o Django roda assim: o **Gunicorn** executa o projeto como um serviço do systemd, escutando em `127.0.0.1:8000`; o **Nginx** recebe o tráfego, serve a pasta `/static/` direto do disco e repassa o resto ao Gunicorn; o certbot cuida do HTTPS. No projeto, deixe `DEBUG = False`, liste o domínio em `ALLOWED_HOSTS`, leia a `SECRET_KEY` de uma variável de ambiente e rode `migrate` e `collectstatic`.

## Como as peças se encaixam

Um site Django em produção tem quatro camadas, cada uma com um trabalho só:

| Peça | Função | Onde escuta |
| --- | --- | --- |
| Nginx | Recebe o acesso público, faz o HTTPS, serve os arquivos estáticos e repassa o resto | Portas 80 e 443, abertas ao público |
| Gunicorn | Executa o código Django, com vários processos | 127.0.0.1:8000, só local |
| Django | A sua aplicação, entregue ao Gunicorn pelo módulo wsgi | Dentro do Gunicorn |
| PostgreSQL | Guarda os dados | 127.0.0.1:5432, só local |

O Gunicorn e o banco ficam presos ao endereço local de propósito: só o Nginx é exposto à internet. Os exemplos usam o Ubuntu 24.04, o projeto `meusite` e o domínio `meusite.com.br`. Troque pelos seus.

## Preparar a VPS e o ambiente virtual

Entre na VPS por SSH com um usuário comum que tenha sudo (aqui, o `deploy`) e instale o necessário:

```
sudo apt update
sudo apt install -y python3 python3-venv python3-dev build-essential git nginx

# pasta do projeto fora do /home, para o Nginx conseguir ler o static
sudo mkdir -p /srv/meusite
sudo chown deploy:deploy /srv/meusite
```

Coloque o código em `/srv/meusite`, por `git clone` ou por SFTP, de modo que o `manage.py` fique direto dentro dela. Depois crie o ambiente virtual e instale as dependências:

```
cd /srv/meusite
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
```

O seu `requirements.txt` precisa ter, além do Django, o Gunicorn e o driver do PostgreSQL. Gere o arquivo no computador de desenvolvimento com `pip freeze` ou escreva à mão:

```
Django>=5.0
gunicorn
psycopg[binary]
```

Se o instalador reclamar de um pacote que precisa compilar, os pacotes `build-essential` e `python3-dev`, já instalados acima, cobrem a maioria dos casos.

## Settings de produção

O que mais derruba um deploy de Django é levar o `settings.py` de desenvolvimento para a VPS. Ajuste estes pontos no arquivo do projeto, lendo os segredos do ambiente em vez de escrever esses valores no código:

```
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"

ALLOWED_HOSTS = os.environ["DJANGO_ALLOWED_HOSTS"].split(",")
CSRF_TRUSTED_ORIGINS = [
    "https://meusite.com.br",
    "https://www.meusite.com.br",
]

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"

# o Nginx termina o HTTPS e avisa o Django por este cabeçalho
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
```

- **DEBUG:** com a variável ausente ele fica desligado, que é o comportamento seguro. Ligado em produção, uma página de erro entrega o código, as variáveis e as configurações.
- **SECRET_KEY:** assina sessões e tokens. Se vazar, quem tem a chave forja sessões. Nunca a coloque no Git.
- **ALLOWED_HOSTS:** lista de domínios que o site aceita. Fora dela, o Django devolve erro 400.
- **CSRF_TRUSTED_ORIGINS:** origens aceitas em formulários e no login. Desde o Django 4, cada item precisa vir com o esquema, como https://. Declarar o domínio aqui evita erro 403 no login do admin quando o Django não consegue confirmar sozinho, pelo que o proxy repassa, que a origem é a mesma do site.
- **STATIC_ROOT:** pasta para onde o `collectstatic` copia todos os arquivos estáticos, inclusive os do admin. É essa pasta que o Nginx vai servir.

Agora crie o arquivo com as variáveis. Ele fica fora do projeto, em `/etc/meusite.env`, para nunca entrar em um commit. Gere a chave secreta com Python e use valores sem espaços e sem aspas:

```
python3 -c "import secrets; print(secrets.token_urlsafe(50))"

sudo nano /etc/meusite.env
```

```
DJANGO_SECRET_KEY=cole_aqui_a_chave_gerada
DJANGO_ALLOWED_HOSTS=meusite.com.br,www.meusite.com.br
DB_NAME=meusite
DB_USER=meusite
DB_PASSWORD=troque_por_uma_senha_longa
```

```
# só o root e o usuário deploy leem
sudo chown root:deploy /etc/meusite.env
sudo chmod 640 /etc/meusite.env
```

## PostgreSQL

Instale o PostgreSQL seguindo o guia de [instalar PostgreSQL na VPS Ubuntu](https://streethosting.com.br/guias/vps/instalar-postgresql-vps-ubuntu). Com ele rodando, crie o usuário e o banco que o Django vai usar, com os mesmos dados do arquivo de variáveis:

```
sudo -u postgres psql

CREATE USER meusite WITH PASSWORD 'troque_por_uma_senha_longa';
CREATE DATABASE meusite OWNER meusite;
\q
```

O banco escuta só em 127.0.0.1 por padrão, e é assim que deve ficar. Com as variáveis carregadas no terminal, rode as tarefas de preparação:

```
cd /srv/meusite
source venv/bin/activate

# carrega as variáveis do arquivo na sessão atual
set -a
source /etc/meusite.env
set +a

python manage.py check --deploy
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py createsuperuser
```

O `check --deploy` lista avisos de segurança. Alguns, como o de redirecionamento para HTTPS, só fazem sentido depois do passo do certificado, mais abaixo.

## Gunicorn como serviço systemd

Teste antes de transformar em serviço. Com o ambiente ativo e as variáveis carregadas, rode `gunicorn meusite.wsgi:application --bind 127.0.0.1:8000`. O nome antes dos dois pontos é a pasta do projeto, a que tem o `wsgi.py`. Se subiu sem erro, pare com Ctrl+C e crie o serviço, como no guia de [criar um serviço systemd](https://streethosting.com.br/guias/vps/criar-servico-systemd-linux):

```
sudo nano /etc/systemd/system/meusite.service
```

```
[Unit]
Description=Gunicorn do meusite
After=network.target postgresql.service

[Service]
User=deploy
Group=www-data
WorkingDirectory=/srv/meusite
EnvironmentFile=/etc/meusite.env
ExecStart=/srv/meusite/venv/bin/gunicorn meusite.wsgi:application --workers 3 --bind 127.0.0.1:8000
Restart=on-failure
RestartSec=3

[Install]
WantedBy=multi-user.target
```

```
sudo systemctl daemon-reload
sudo systemctl enable --now meusite
sudo systemctl status meusite
curl -I http://127.0.0.1:8000
```

O `enable` faz o serviço subir sozinho no boot e o `Restart=on-failure` o levanta se ele cair. Sobre os workers: a regra comum é duas vezes o número de vCPUs mais um, mas em uma VPS de 2 GB de RAM comece com 2 ou 3 e acompanhe a memória.

> **Dica**
>
> Em vez da porta 8000, o Gunicorn também pode escutar em um socket Unix, com `--bind unix:/run/meusite/gunicorn.sock --umask 007` mais `RuntimeDirectory=meusite` na unit, e o Nginx usa `proxy_pass http://unix:/run/meusite/gunicorn.sock;`. O `Group=www-data` da unit é o que deixa o Nginx abrir o socket: o arquivo nasce com o grupo www-data, e o umask 007 dá leitura e escrita ao grupo e nada aos demais. É um pouco mais rápido e não ocupa porta. Para começar, a porta local é mais fácil de testar com curl e de diagnosticar.

## Nginx e HTTPS

Crie o site no Nginx. Aponte antes o DNS do domínio para o IP da VPS, como explica o guia de [apontar um domínio para a VPS](https://streethosting.com.br/guias/vps/apontar-dominio-vps-registro-dns). O arquivo é `/etc/nginx/sites-available/meusite`:

```
server {
    listen 80;
    server_name meusite.com.br www.meusite.com.br;

    client_max_body_size 20M;

    location /static/ {
        alias /srv/meusite/staticfiles/;
        expires 30d;
    }

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

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

- **O alias termina com barra**, igual ao caminho do `location`. Sem a barra final o Nginx monta o caminho errado e os arquivos dão 404.
- **Proxy e HTTPS:** o cabeçalho `X-Forwarded-Proto` é o que o `SECURE_PROXY_SSL_HEADER` do settings espera. Sem ele o Django acha que a conexão é HTTP puro e os cookies seguros e a verificação de CSRF dão problema.
- **client_max_body_size** limita o tamanho de uploads. O padrão do Nginx é 1 MB, pouco para quem envia imagens.

Para o HTTPS, instale o certificado com o certbot, como mostra o guia de [certificado SSL gratuito com Let's Encrypt no Nginx](https://streethosting.com.br/guias/vps/certificado-ssl-vps-lets-encrypt-nginx). O comando costuma ser `sudo certbot --nginx -d meusite.com.br -d www.meusite.com.br`. Ele edita o arquivo do site, acrescenta o bloco 443 e o redirecionamento do 80. Até esse passo, o login do admin por HTTP não mantém a sessão, porque o settings marca os cookies como seguros. É o esperado. Para os conceitos por trás do proxy, veja também o guia de [Nginx como proxy reverso](https://streethosting.com.br/guias/vps/configurar-nginx-reverse-proxy-vps).

Com o HTTPS no ar, rode de novo o `check --deploy`. Se quiser, ative no settings o `SECURE_HSTS_SECONDS`, começando com um valor baixo, porque o HSTS faz o navegador lembrar do HTTPS por esse tempo.

## Atualizar e diagnosticar

A cada versão nova do código, a sequência é sempre a mesma:

```
cd /srv/meusite
git pull
source venv/bin/activate
pip install -r requirements.txt

set -a
source /etc/meusite.env
set +a

python manage.py migrate
python manage.py collectstatic --noinput
sudo systemctl restart meusite
```

Quando algo não abre, comece pelo log do serviço, com `sudo journalctl -u meusite -n 50 --no-pager`, e pelo log do Nginx, em `/var/log/nginx/error.log`. Os sintomas mais comuns:

| Sintoma | Causa provável |
| --- | --- |
| 502 Bad Gateway | O Gunicorn não está rodando ou a porta do proxy_pass está errada. Veja status e journalctl. O guia do erro 502 no Nginx detalha o diagnóstico. |
| 400 Bad Request | O domínio não está no ALLOWED_HOSTS. |
| 403 no login ou em formulário | Falta CSRF_TRUSTED_ORIGINS com https://, ou o Nginx não manda o X-Forwarded-Proto. |
| Admin sem CSS | Não rodou o collectstatic, ou o alias do /static/ aponta para a pasta errada. |
| 500 sem detalhe | Erro no código. O traceback está no journalctl, nunca ligue DEBUG para descobrir. |
| Erro de conexão com o banco | Variáveis do DB erradas no arquivo de ambiente, ou o usuário do PostgreSQL sem o banco como dono. |

> **Atenção**
>
> O arquivo de ambiente é lido só quando o serviço inicia. Depois de editar o `/etc/meusite.env`, rode `sudo systemctl restart meusite`, ou a mudança parece não ter feito efeito. Para o erro 502 em detalhe, veja o guia de [erro 502 Bad Gateway no Nginx](https://streethosting.com.br/guias/vps/erro-502-bad-gateway-nginx).

## Onde rodar isso

Esse caminho exige instalar pacotes, criar um serviço do systemd e editar a configuração do Nginx, tudo com root. Nas [VPS da StreetHosting](https://streethosting.com.br/vps) você tem acesso root em um servidor KVM com kernel próprio, então segue o guia como está. Todas ficam em São Paulo, com AntiDDoS incluso, e ativam em cerca de 60 segundos depois do Pix ou do cartão aprovado.

Um projeto Django pequeno com PostgreSQL roda bem na VPS Xeon a partir de R$ 26,00 por mês, com 2 vCPU, 2 GB DDR4 e 20 GB NVMe. A memória é o recurso a vigiar: cada worker do Gunicorn e o PostgreSQL consomem RAM. Acompanhe com `free -h` depois do deploy e ajuste o número de workers conforme a folga.

## Perguntas frequentes

### Por que não posso usar o runserver em produção?

O runserver foi feito para desenvolvimento. A própria documentação do Django avisa que ele não passou por auditoria de segurança nem foi pensado para desempenho, e ele reinicia sozinho ao mexer em arquivos. Em produção o Gunicorn recebe as requisições da aplicação e o Nginx fica na frente, cuidando de HTTPS, arquivos estáticos e conexões lentas.

### Quantos workers do Gunicorn devo usar?

A regra comum é duas vezes o número de vCPUs mais um. Em uma VPS pequena, 2 ou 3 workers bastam. Cada worker é um processo com a aplicação carregada na memória, então confira o uso de RAM antes de aumentar.

### O Django não mostra meu CSS e minhas imagens do admin. O que fazer?

Com DEBUG desligado o Django não serve arquivos estáticos. Defina STATIC_ROOT, rode collectstatic e faça o Nginx servir essa pasta no caminho /static/. Se a página abre mas o estilo não carrega, quase sempre é o alias do Nginx apontando para a pasta errada ou a pasta sem permissão de leitura.

### Recebi o erro 400 Bad Request ao abrir o site. É o Nginx?

Provavelmente é o Django. Com DEBUG desligado, ele recusa qualquer Host que não esteja em ALLOWED_HOSTS. Inclua o domínio, com e sem www, e reinicie o Gunicorn. O log do serviço mostra a mensagem DisallowedHost.

### Posso usar SQLite em produção no Django?

Para um site pequeno com pouca escrita funciona, e o backup é copiar um arquivo. Quando há várias escritas simultâneas ou o projeto cresce, o PostgreSQL é mais seguro e permite backup sem parar a aplicação.

## Guias relacionados

- [Como configurar o Nginx como reverse proxy na VPS](https://streethosting.com.br/guias/vps/configurar-nginx-reverse-proxy-vps.md)
- [Como instalar PostgreSQL na VPS Ubuntu com segurança](https://streethosting.com.br/guias/vps/instalar-postgresql-vps-ubuntu.md)
- [Certificado SSL na VPS com Let's Encrypt e Nginx](https://streethosting.com.br/guias/vps/certificado-ssl-vps-lets-encrypt-nginx.md)
- [Como criar um serviço systemd no Linux para rodar qualquer app](https://streethosting.com.br/guias/vps/criar-servico-systemd-linux.md)
- [Erro 502 Bad Gateway no Nginx: causas e como corrigir](https://streethosting.com.br/guias/vps/erro-502-bad-gateway-nginx.md)

## Produtos citados

- https://streethosting.com.br/vps

## Dados estruturados

```json
[
  {
    "@context": "https://schema.org",
    "@type": [
      "Article",
      "TechArticle"
    ],
    "headline": "Como hospedar um projeto Django na VPS com Gunicorn, Nginx e PostgreSQL",
    "name": "Hospedar Django na VPS com Gunicorn, Nginx e PostgreSQL",
    "abstract": "O runserver não serve para produção. Veja o caminho completo na VPS: ambiente virtual, settings de produção, Gunicorn sob o systemd, Nginx servindo o /static/ e HTTPS com certbot.",
    "description": "Coloque um projeto Django em produção na VPS Ubuntu: venv, settings seguros, Gunicorn como serviço systemd, Nginx com arquivos estáticos, HTTPS e PostgreSQL.",
    "datePublished": "2026-11-13",
    "dateModified": "2026-11-13",
    "author": {
      "@type": "Organization",
      "name": "Equipe StreetHosting"
    },
    "publisher": {
      "@type": "Organization",
      "name": "StreetHosting",
      "url": "https://streethosting.com.br"
    },
    "inLanguage": "pt-BR",
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://streethosting.com.br/guias/vps/hospedar-django-vps-gunicorn-nginx"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "Por que não posso usar o runserver em produção?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "O runserver foi feito para desenvolvimento. A própria documentação do Django avisa que ele não passou por auditoria de segurança nem foi pensado para desempenho, e ele reinicia sozinho ao mexer em arquivos. Em produção o Gunicorn recebe as requisições da aplicação e o Nginx fica na frente, cuidando de HTTPS, arquivos estáticos e conexões lentas."
        }
      },
      {
        "@type": "Question",
        "name": "Quantos workers do Gunicorn devo usar?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "A regra comum é duas vezes o número de vCPUs mais um. Em uma VPS pequena, 2 ou 3 workers bastam. Cada worker é um processo com a aplicação carregada na memória, então confira o uso de RAM antes de aumentar."
        }
      },
      {
        "@type": "Question",
        "name": "O Django não mostra meu CSS e minhas imagens do admin. O que fazer?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Com DEBUG desligado o Django não serve arquivos estáticos. Defina STATIC_ROOT, rode collectstatic e faça o Nginx servir essa pasta no caminho /static/. Se a página abre mas o estilo não carrega, quase sempre é o alias do Nginx apontando para a pasta errada ou a pasta sem permissão de leitura."
        }
      },
      {
        "@type": "Question",
        "name": "Recebi o erro 400 Bad Request ao abrir o site. É o Nginx?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Provavelmente é o Django. Com DEBUG desligado, ele recusa qualquer Host que não esteja em ALLOWED_HOSTS. Inclua o domínio, com e sem www, e reinicie o Gunicorn. O log do serviço mostra a mensagem DisallowedHost."
        }
      },
      {
        "@type": "Question",
        "name": "Posso usar SQLite em produção no Django?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Para um site pequeno com pouca escrita funciona, e o backup é copiar um arquivo. Quando há várias escritas simultâneas ou o projeto cresce, o PostgreSQL é mais seguro e permite backup sem parar a aplicação."
        }
      }
    ]
  }
]
```
