Nos artigos anteriores, entendemos como o Nginx funciona e configuramos o servidor do zero. O servidor está rodando, mas ainda faltam duas coisas para uma aplicação de produção real: deploy automatizado e HTTPS.
Neste artigo vamos resolver os dois. Ao final, cada git push na branch main vai:
- Enviar os arquivos atualizados para o servidor via SSH
- Validar a configuração do Nginx (
nginx -t) - Recarregar o Nginx sem derrubar conexões ativas
E o site vai estar servindo HTTPS com certificado gratuito e renovação automática via Certbot.
Parte 1 — Deploy automático com GitHub Actions
A ideia
O arquivo infra/nginx.conf fica no repositório. A cada push, o GitHub Actions envia os arquivos para /var/www/meusite.com/ via rsync (ou scp) e executa nginx -t && systemctl reload nginx remotamente via SSH.
Nenhum acesso manual ao servidor é necessário depois da configuração inicial.
Estrutura do repositório
meusite/
├── infra/
│ └── nginx.conf # configuração do Nginx versionada
├── html/
│ ├── index.html
│ └── estilo.css
└── .github/
└── workflows/
└── deploy.yml
1.1 Criar um usuário dedicado para deploy no servidor
Nunca use root para deploy. Crie um usuário com acesso restrito:
sudo adduser deployuser
sudo mkdir -p /home/deployuser/.ssh
sudo chmod 700 /home/deployuser/.ssh
Dê permissão de escrita apenas no diretório do projeto:
sudo chown -R deployuser:deployuser /var/www/meusite.com
Configure o sudo restrito para os comandos do Nginx:
echo "deployuser ALL=(ALL) NOPASSWD: /usr/sbin/nginx, /bin/systemctl reload nginx" \
| sudo tee /etc/sudoers.d/nginx-deploy
1.2 Configurar a chave SSH
Na sua máquina local, gere um par de chaves para o deploy:
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/deploy_key
Adicione a chave pública ao servidor:
sudo -u deployuser bash -c 'cat >> /home/deployuser/.ssh/authorized_keys' < ~/.ssh/deploy_key.pub
sudo chmod 600 /home/deployuser/.ssh/authorized_keys
1.3 Adicionar os secrets no GitHub
No repositório, acesse Settings → Secrets and variables → Actions e adicione:
| Secret | Valor |
|---|---|
SERVER_HOST | IP ou domínio do servidor |
SERVER_USER | deployuser |
SERVER_SSH_KEY | Conteúdo de ~/.ssh/deploy_key (chave privada) |
1.4 Criar o workflow de deploy
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Enviar arquivos para o servidor
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
source: "html/,infra/"
target: /var/www/meusite.com/
strip_components: 0
- name: Validar e recarregar Nginx
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
sudo nginx -t && sudo systemctl reload nginx
Por que
nginx -tantes do reload? Se oinfra/nginx.conftiver um erro de sintaxe, oreloadfalha e o servidor continua rodando com a config anterior. Onginx -tvalida antes de tentar recarregar. Se a validação falhar, o workflow para e o problema aparece no log do Actions — sem impacto no site em produção.
1.5 Testar o deploy
Faça uma alteração qualquer no html/index.html, commit e push:
git add .
git commit -m "test: primeiro deploy automático"
git push origin main
Acompanhe a execução em Actions no GitHub. Se tudo correr bem, você verá os dois steps verdes e o site atualizado.
Parte 2 — HTTPS com Certbot
2.1 Instalar o Certbot
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
2.2 Garantir que o domínio está apontando para o servidor
O Certbot vai fazer uma verificação HTTP no domínio antes de emitir o certificado. O DNS precisa estar propagado.
Verifique:
dig +short meusite.com
# deve retornar o IP do seu servidor
2.3 Emitir o certificado
sudo certbot --nginx -d meusite.com -d www.meusite.com
O Certbot vai:
- Verificar a propriedade do domínio via HTTP
- Emitir o certificado via Let's Encrypt
- Modificar automaticamente o
nginx.confdo site para adicionar HTTPS e redirecionar HTTP → HTTPS
Após o processo, o arquivo infra/nginx.conf terá sido atualizado pelo Certbot com algo assim:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name meusite.com www.meusite.com;
ssl_certificate /etc/letsencrypt/live/meusite.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/meusite.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/meusite.com/html;
# ... resto da config ...
}
server {
listen 80;
listen [::]:80;
server_name meusite.com www.meusite.com;
return 301 https://$host$request_uri;
}
Importante: como o Certbot modifica o arquivo no servidor, após esse passo você deve copiar o conteúdo atualizado de volta para o repositório. A partir daí, o arquivo no repo já terá as diretivas SSL e o deploy automático vai mantê-las.
2.4 Verificar a renovação automática
Os certificados do Let's Encrypt expiram em 90 dias. O Certbot instala um timer systemd que renova automaticamente antes do vencimento.
Verifique se o timer está ativo:
sudo systemctl status certbot.timer
Simule uma renovação para confirmar que funciona:
sudo certbot renew --dry-run
Fluxo completo após a configuração
Com tudo configurado, o ciclo de trabalho fica assim:
Edita código ou nginx.conf
│
▼
git push main
│
▼
GitHub Actions
├── scp: envia html/ e infra/ para /var/www/meusite.com/
└── ssh: nginx -t && systemctl reload nginx
│
▼
Site atualizado em produção
sem acesso manual ao servidor
Checklist final
Antes de considerar o servidor pronto para produção, verifique:
- [ ]
sudo nginx -tretorna syntax ok - [ ] Site acessível via HTTPS no navegador
- [ ] Redirecionamento HTTP → HTTPS funcionando
- [ ]
sudo certbot renew --dry-runsem erros - [ ] Deploy automático funcionando (push → Actions verde → site atualizado)
- [ ] Logs de acesso e erro sendo escritos corretamente
- [ ]
server_tokens offativo (oculta versão do Nginx) - [ ] Usuário
deployusersem acesso root (apenas sudo restrito)
Próximos passos
Com essa base, você pode evoluir para:
- Rate limiting — protege endpoints de login e APIs contra abuso com
limit_req_zone - Load balancing — distribui tráfego entre múltiplas instâncias com o bloco
upstream - Proxy para backend — configura o Nginx como reverse proxy para Node.js, Python ou Go
- Cabeçalhos de segurança — adiciona
X-Frame-Options,Content-Security-Policye similares
Referências: