Ir para o conteúdo

Nginx — Coaching app e documentação

Utiliza estes exemplos como base no mesmo servidor da app (Next.js + pm2) e do site MkDocs em docs.diasantos.com.

Ficheiros no repositório

Os mesmos conteúdos estão em documentation/examples/nginx/ para copiares para /etc/nginx/sites-available/ sem passar pelo browser.

Activar um site no nginx (Debian / Ubuntu)

Fluxo típico para cada domínio (substitui docs.diasantos.com pelo nome do ficheiro que quiseres; para a app usa por exemplo coaching.app ou o domínio real).

1. Instalar o nginx (se ainda não existir)

sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx

2. Criar o ficheiro do virtual host

Opção A — copiar o exemplo do repositório (ajusta o caminho do clone):

sudo cp /var/www/coaching/documentation/examples/nginx/docs.diasantos.com.conf.example \
  /etc/nginx/sites-available/docs.diasantos.com

Opção B — criar/editar à mão com o editor que preferires:

sudo vim /etc/nginx/sites-available/docs.diasantos.com
# ou: sudo nano /etc/nginx/sites-available/docs.diasantos.com

Cola o conteúdo do exemplo (secções abaixo ou ficheiro .example), grava e sai (:wq no vim, Ctrl+O / Ctrl+X no nano).

Antes do primeiro HTTPS

Se ainda não tens certificados, podes começar só com o bloco listen 80 e server_name, servir o site em HTTP, emitir o certificado com certbot, e só depois acrescentar o bloco listen 443 ssl ou deixar o certbot alterar o ficheiro por ti. Os exemplos assumem que os paths do Let's Encrypt já existem.

O nginx só carrega o que estiver em sites-enabled/. Cria um link para o ficheiro em sites-available/:

sudo ln -sf /etc/nginx/sites-available/docs.diasantos.com \
  /etc/nginx/sites-enabled/docs.diasantos.com
  • -f substitui o link se já existir (útil ao repetir o comando).
  • Repete o mesmo padrão para a app, por exemplo: /etc/nginx/sites-available/coaching.appsites-enabled/coaching.app.

4. Conflito com o site por defeito (opcional)

Em instalações novas, /etc/nginx/sites-enabled/default pode ocupar a porta 80. Se precisares dessa porta para o teu server_name, desactiva o default:

sudo rm /etc/nginx/sites-enabled/default
# ou: sudo unlink /etc/nginx/sites-enabled/default

5. Validar e aplicar

sudo nginx -t

Se aparecer syntax is ok e test is successful:

sudo systemctl reload nginx

Confirma no browser (https://docs.diasantos.com) ou com curl -I https://docs.diasantos.com.

6. Ver erros se algo falhar

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

Coaching (reverse proxy para Next.js)

Pressupõe next start ou equivalente em 127.0.0.1:3000 (porta típica do pm2). Ajusta server_name, caminhos dos certificados e a proxy_pass se a porta for outra.

# Exemplo: reverse proxy HTTPS para a app Next.js (pm2 em 127.0.0.1:3000).
#
# Uso no servidor Debian/Ubuntu:
#   sudo cp coaching.app.conf.example /etc/nginx/sites-available/coaching.app
#   sudo ln -sf /etc/nginx/sites-available/coaching.app /etc/nginx/sites-enabled/
#   sudo nginx -t && sudo systemctl reload nginx
#
# Substitui app.diasantos.com pelo teu domínio da aplicação.
# Certificados: certbot certonly --nginx -d app.diasantos.com

server {
    listen 80;
    listen [::]:80;
    server_name app.diasantos.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name app.diasantos.com;

    ssl_certificate     /etc/letsencrypt/live/app.diasantos.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.diasantos.com/privkey.pem;

    # Opcional: endurecer TLS (ajusta à tua política)
    # ssl_protocols TLSv1.2 TLSv1.3;

    client_max_body_size 25m;

    # POST /api/admin/deploy — SSE de longa duração (`next build` pode passar de 5–10 min com poucos bytes).
    # Um `proxy_read_timeout` curto fecha a ligação ao upstream → no browser **net::ERR_INCOMPLETE_CHUNKED_ENCODING**.
    location = /api/admin/deploy {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;
        proxy_buffering off;
        proxy_cache off;
        gzip off;
        proxy_read_timeout 7200s;
        proxy_send_timeout 7200s;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 300;
    }
}

A app também envia comentários SSE periódicos durante os passos do deploy para o proxy ver leituras regulares; convém manter proxy_read_timeout alto neste caminho no nginx.

Documentação (MkDocs estático em docs.diasantos.com)

Depois de mkdocs build, o conteúdo fica em documentation/site/. O root abaixo deve apontar para essa pasta no servidor.

# Exemplo: site estático MkDocs (saída de: cd documentation && mkdocs build).
#
# Uso no servidor Debian/Ubuntu:
#   sudo cp docs.diasantos.com.conf.example /etc/nginx/sites-available/docs.diasantos.com
#   sudo ln -sf /etc/nginx/sites-available/docs.diasantos.com /etc/nginx/sites-enabled/
#   sudo nginx -t && sudo systemctl reload nginx
#
# Ajusta root ao caminho real do clone (APP_DIR/documentation/site).
# Certificados: certbot certonly --nginx -d docs.diasantos.com

server {
    listen 80;
    listen [::]:80;
    server_name docs.diasantos.com;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name docs.diasantos.com;

    ssl_certificate     /etc/letsencrypt/live/docs.diasantos.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/docs.diasantos.com/privkey.pem;

    root /var/www/coaching/documentation/site;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    # Opcional: cache curto para assets estáticos gerados pelo MkDocs
    location ~* \.(?:css|js|woff2?|png|jpg|jpeg|gif|svg|ico)$ {
        try_files $uri =404;
        expires 7d;
        add_header Cache-Control "public, max-age=604800, immutable";
    }
}

Certificados com Certbot (Let's Encrypt)

Usa o plugin nginx do Certbot para emitir e renovar certificados sem parar o servidor de forma desnecessária (desafio via nginx).

Pré-requisitos

  • DNS: registos A (e opcionalmente AAAA) para app.diasantos.com e docs.diasantos.com a apontar para o IP público deste servidor (substitui pelos teus domínios reais).
  • Porta 80 acessível de Internet (Let's Encrypt usa HTTP-01 por defeito com --nginx).
  • Nginx a correr com pelo menos o bloco HTTP (porta 80) e server_name correcto antes de pedires HTTPS — se o ficheiro tiver ssl_certificate para ficheiros que ainda não existem, o nginx -t falha; nesse caso começa só com listen 80 (como no aviso da secção de activação), obtém o certificado, e só depois acrescenta ou descomenta o bloco 443 dos exemplos.

Instalar o Certbot

sudo apt update
sudo apt install -y certbot python3-certbot-nginx

Emitir um certificado por site (recomendado)

Com os dois virtual hosts activos em sites-enabled e HTTP a responder para cada server_name:

# App (reverse proxy Next.js)
sudo certbot --nginx -d app.diasantos.com

# Documentação (MkDocs estático)
sudo certbot --nginx -d docs.diasantos.com

O Certbot pergunta email, aceitação dos termos e se queres redireccionar HTTP→HTTPS (equivalente aos return 301 dos exemplos). No fim, costuma ajustar os teus server { ... } para usar fullchain.pem e privkey.pem em /etc/letsencrypt/live/<domínio>/.

Alinhar com os exemplos desta página

Se já tinhas colado os blocos completos dos exemplos (proxy, root, cabeçalhos), faz backup antes (sudo cp …/sites-available/… /tmp/) e, depois do Certbot, verifica com sudo nginx -t. Se o Certbot simplificar demasiado um bloco (por exemplo perder proxy_set_header na app), recupera essas linhas do backup e mantém os ssl_certificate que o Certbot configurou.

Um único certificado para os dois nomes (opcional)

Útil se quiseres um só certificado SAN para ambos os hosts:

sudo certbot --nginx -d app.diasantos.com -d docs.diasantos.com

Nesse caso ambos os server { … } em HTTPS devem referenciar o mesmo par fullchain.pem / privkey.pem (normalmente o primeiro nome da lista define o directório em /etc/letsencrypt/live/). Os exemplos separados por domínio assumem um certificado por site (um server_name por certificado).

Verificar e recarregar

sudo nginx -t && sudo systemctl reload nginx
sudo certbot certificates

Renovação automática

O pacote instala normalmente um timer systemd que corre certbot renew:

sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Em Debian/Ubuntu, após renovação bem-sucedida o hook costuma recarregar o nginx; se alterares configuração manualmente, confirma que renew continua a funcionar com o dry-run.

Alternativa: só obter o certificado (sem o plugin alterar o nginx)

Se preferires emitir o certificado e tu editares o nginx à mão:

sudo certbot certonly --nginx -d app.diasantos.com
sudo certbot certonly --nginx -d docs.diasantos.com

Depois define ssl_certificate / ssl_certificate_key como nos exemplos abaixo (paths sob /etc/letsencrypt/live/<domínio>/).

Teste rápido depois de tudo

curl -fsSI https://app.diasantos.com | head -n 5
curl -fsSI https://docs.diasantos.com | head -n 5

Se não usares /var/www/certbot no bloco /.well-known/acme-challenge/ e usares webroot manualmente, alinha root com o webroot que passares ao Certbot.

Ver também