Instalação e Hardening do Nginx no Debian 13 (Trixie)¶
Este guia instala o Nginx a partir do repositório oficial do nginx.org, faz o hardening do SSH, configura o firewall com nftables e publica o site em HTTPS com certificado emitido por uma CA própria, sem depender de Cloudflare nem de domínio público.
Pré-requisitos:
- Debian 13 instalado, com acesso à internet
- Senha do root ou um usuário que já tenha permissão de
sudo(o Passo 0 configura isso) - Um computador cliente (Linux ou Windows) na mesma rede para acessar o servidor
Valores usados neste guia. Troque pelos do seu ambiente sempre que aparecerem nos comandos:
| Item | Valor no guia |
|---|---|
| IP do servidor | 192.168.1.10 |
| Nome do servidor | nginx.example.internal |
| Usuário | seu_usuario |
| Porta do SSH | 2222 |
O sufixo .internal é reservado pela ICANN para uso em redes privadas e nunca vai existir na internet, então não há risco de conflito com um domínio real.
O que é o Nginx¶
O Nginx é um servidor web: ele recebe requisições HTTP e HTTPS e devolve o conteúdo pedido. Esse conteúdo pode ser um arquivo do disco (HTML, imagem, CSS) ou a resposta de outra aplicação que está rodando atrás dele. Nesse segundo caso, o Nginx atua como proxy reverso.
Na prática, você vai usar o Nginx para duas coisas:
- Hospedar sites, um ou vários no mesmo servidor
- Publicar a interface web de outras aplicações, como o front-end do Zabbix, GLPI ou qualquer sistema que rode em uma porta interna
Como uma requisição chega até o Nginx¶
- O usuário digita
exemplo.com.brno navegador. O DNS traduz esse nome para o endereço IP do servidor. - O navegador abre uma conexão TCP com esse IP, na porta 80 (HTTP) ou 443 (HTTPS). É aqui que acontece o three-way handshake do TCP: SYN, SYN-ACK, ACK. Sem ele não existe conexão, e nenhum dado do site trafega.
- Se for HTTPS, acontece o handshake TLS, que negocia a criptografia antes de qualquer conteúdo ser enviado. Nessa etapa o navegador informa o nome do site que quer acessar (o SNI) e confere o certificado do servidor.
- O navegador envia a requisição HTTP. Dentro dela vai o cabeçalho
Host: exemplo.com.br. - O Nginx lê o nome pedido e decide qual site (bloco
server) responde. Depois devolve o arquivo do disco ou repassa a requisição para a aplicação de trás.
O passo 5 é o que permite hospedar vários sites em um único IP. Funciona como a recepção de um prédio comercial: o endereço é o mesmo para todas as empresas, e a recepção pergunta para qual sala você vai antes de liberar a entrada.
As portas 80 e 443 são as padrão, mas o Nginx pode escutar em qualquer porta que você definir na configuração.
Parte 1: Instalação¶
Passo 0: Instalar o sudo e dar permissão ao seu usuário¶
O sudo permite que um usuário comum execute comandos administrativos, um de cada vez, sem precisar logar como root. A senha pedida é a do próprio usuário, e cada comando executado fica registrado no log do sistema.
No Debian, isso depende de uma escolha feita na instalação. Se você definiu uma senha para o root, o sudo não vem instalado e o seu usuário não tem permissão administrativa. Se deixou a senha do root em branco, o instalador já fez tudo isso e você pode pular para o Passo 1.
0.1 Verificar se este passo é necessário¶
Logado com o seu usuário:
Se sudo aparecer na lista, pule para o Passo 1. Se não aparecer, continue.
0.2 Entrar como root¶
Digite a senha do root, definida na instalação. O hífen faz diferença: su - carrega o ambiente completo do root, incluindo a pasta /usr/sbin no caminho de busca de comandos. Com su sem hífen, comandos administrativos como o usermod retornam command not found.
Se você está acessando por SSH, entre com o seu usuário e rode o su - dentro da sessão. O SSH do Debian já vem bloqueando login do root com senha.
0.3 Instalar o sudo¶
Os comandos vão sem sudo na frente porque você já está como root.
0.4 Adicionar o seu usuário ao grupo sudo¶
-G sudo: coloca o usuário no gruposudo. O Debian já traz no arquivo/etc/sudoersa regra que libera esse grupo para executar qualquer comando como root, então não é preciso editar o sudoers.-a: adiciona o grupo sem remover os que o usuário já tem. Sem o-a, o usuário sai de todos os outros grupos e fica só nosudo.
0.5 Sair e entrar de novo¶
Os grupos de um usuário são lidos no momento do login. A sessão que já estava aberta não enxerga o grupo novo.
Esse primeiro exit sai do root. Depois encerre a sessão do seu usuário (exit de novo, ou feche a conexão SSH) e faça login outra vez.
0.6 Testar¶
Saída esperada:
A lista de grupos varia de instalação para instalação. O que importa é sudo estar nela e o sudo whoami responder root depois de pedir a sua senha.
É este usuário que vai administrar o servidor daqui em diante. É por isso que o Passo 10 consegue bloquear o login do root pelo SSH sem você perder o acesso.
Passo 1: Instalar os pré-requisitos¶
curl: baixa a chave de assinatura do Nginxgnupg2: converte e verifica a chaveca-certificates: permite validar o certificado HTTPS do nginx.orglsb-release: fornece o comando que identifica a versão do Debian (trixie)
Passo 2: Importar a chave de assinatura do Nginx¶
Todo pacote do repositório oficial é assinado digitalmente. A chave importada aqui é o que permite ao apt confirmar que o pacote veio mesmo do nginx.org e não foi alterado no caminho.
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
O gpg --dearmor converte a chave do formato texto para o formato binário que o apt lê. A flag -f faz o curl falhar se o download der erro, em vez de gravar uma página de erro no lugar da chave.
Passo 3: Conferir a impressão digital da chave¶
Em uma instalação nova, o seu usuário ainda não tem a pasta de configuração do GnuPG, e o gpg encerra com Fatal: /home/seu_usuario/.gnupg: directory does not exist!. Crie a pasta antes:
O -m 700 deixa a pasta acessível só para o seu usuário. Com permissão mais aberta, o gpg reclama que a pasta é insegura.
gpg --dry-run --quiet --no-keyring --import --import-options import-show \
/usr/share/keyrings/nginx-archive-keyring.gpg
A saída precisa conter a impressão digital 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62:
pub rsa4096 2024-05-29 [SC]
8540A6F18833A80E9C1653A42FD21310B49F6B46
uid nginx signing key <signing-key-2@nginx.com>
pub rsa2048 2011-08-19 [SC] [expires: 2027-05-24]
573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62
uid nginx signing key <signing-key@nginx.com>
pub rsa4096 2024-05-29 [SC]
9E9BE90EACBCDE69FE9B204CBCDCD8A38D88A2B3
uid nginx signing key <signing-key-3@nginx.com>
As outras duas chaves (signing-key-2 e signing-key-3) também são do nginx.org e assinam outros pacotes. Isso é normal. Se a impressão digital apareceu, siga direto para o Passo 4.
Só se a impressão digital
573BFD...BF62NÃO aparecer: a chave baixada não é a oficial. Apague o arquivo comsudo rm /usr/share/keyrings/nginx-archive-keyring.gpge não continue. Se a chave estiver correta, não rode esse comando: sem o arquivo, oaptrecusa o repositório do nginx.org e instala o pacote do Debian no lugar.
Passo 4: Adicionar o repositório estável do Nginx¶
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
https://nginx.org/packages/debian $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
signed-by=: amarra esse repositório à chave do Passo 2. Pacote assinado com outra chave é recusado.$(lsb_release -cs): retornatrixie, o codinome do Debian 13.
Passo 5: Dar prioridade ao repositório do nginx.org¶
O Debian 13 também tem um pacote nginx no repositório dele, em versão mais antiga. Sem esta regra, o apt pode instalar o pacote do Debian em vez do oficial.
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
| sudo tee /etc/apt/preferences.d/99nginx
A prioridade 900 é maior que a padrão dos repositórios do Debian (500), então o apt sempre escolhe o pacote do nginx.org.
Passo 6: Instalar o Nginx¶
O && só executa o apt install se o apt update terminar sem erro. Se o repositório do nginx.org falhar na verificação da chave, a instalação para ali, em vez de seguir e instalar o pacote do Debian.
Confirme que o pacote instalado veio do nginx.org:
A linha marcada com *** precisa apontar para https://nginx.org/packages/debian trixie/nginx. Se apontar para deb.debian.org, revise os Passos 4 e 5.
Passo 7: Iniciar o serviço e habilitar no boot¶
O enable faz o Nginx subir sozinho quando o servidor reinicia. O --now inicia o serviço agora. Se ele já estiver rodando, o comando não altera nada.
O comando pode mostrar a mensagem Synchronizing state of nginx.service with SysV service script. Ela não é erro: o pacote do nginx.org traz, além da unidade do systemd, um script de inicialização no formato antigo (SysV, em /etc/init.d/nginx), e o systemd só deixa os dois sincronizados. Quem controla o serviço continua sendo o systemd.
Verifique:
Saída esperada:
Passo 8: Testar localmente¶
Saída esperada:
O número da versão muda conforme a release estável atual do nginx.org. Se aparecer nginx/1.26.3, o pacote instalado é o do Debian, e não o oficial: volte ao Passo 5.
Parte 2: Hardening do SSH¶
O SSH é a porta de entrada administrativa do servidor. Do jeito que vem instalado, ele aceita login por senha na porta 22, e essa combinação é exatamente o que os robôs de força bruta procuram. Nesta parte você troca a senha por chave criptográfica, bloqueia o login direto do root e muda a porta.
A ordem dos passos importa: primeiro a chave funciona, depois a senha é desligada. Inverter isso é a forma mais comum de perder o acesso ao servidor.
Passo 9: Criar e instalar a chave SSH¶
9.1 Gerar a chave no seu computador (cliente)¶
Rode no seu computador, não no servidor.
Linux:
Windows (PowerShell):
-t ed25519: algoritmo de chave moderno, curto e mais rápido que RSA, com segurança equivalente ou maior-C: um comentário gravado no final da chave. Não tem papel nenhum no login, serve só para identificar de qual computador a chave veio. O comando já preenche sozinho com o seu usuário e o nome do seu computador, por exemplousuario@notebook.
Por que o comentário identifica o computador de origem, e não o servidor: no servidor, todas as chaves autorizadas ficam juntas no arquivo ~/.ssh/authorized_keys, uma por linha. Quando um notebook for perdido ou um acesso precisar ser revogado, é pelo comentário que você descobre qual linha apagar.
O comando faz três perguntas:
- Onde salvar a chave. Aperte Enter para aceitar o padrão (
~/.ssh/id_ed25519). Se digitar um nome, a chave é salva na pasta em que você está, e não em~/.ssh. Veja o aviso do 9.2. - Passphrase. Uma senha que protege a chave caso alguém copie o arquivo do seu computador. É local: não tem relação com a senha do servidor.
- Confirmação da passphrase.
O comando cria dois arquivos: id_ed25519 (chave privada, nunca sai do seu computador) e id_ed25519.pub (chave pública, vai para o servidor).
9.2 Copiar a chave pública para o servidor¶
Neste momento o SSH ainda está na porta 22 e aceita senha. No Linux:
No Windows (PowerShell), que não tem ssh-copy-id:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh seu_usuario@192.168.1.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Os dois fazem a mesma coisa: adicionam a chave pública no arquivo ~/.ssh/authorized_keys do seu usuário no servidor, com as permissões que o SSH exige. Se a pasta ou o arquivo tiverem permissão aberta demais, o SSH ignora a chave.
Erros comuns aqui:
- Rodar
ssh-copy-idcomsudo. Osudofaz o comando procurar as chaves do root, não as suas, e o resultado éERROR: No identities found. A chave é do seu usuário, então o comando roda semsudo.- Dar outro nome à chave no
ssh-keygen. Se você digitou um nome em vez de aceitar o caminho padrão, a chave fica salva na pasta onde você estava, não em~/.ssh. Mova a chave para lá e informe o arquivo com-i:mv ~/nome-da-chave ~/nome-da-chave.pub ~/.ssh/ ssh-copy-id -i ~/.ssh/nome-da-chave.pub seu_usuario@192.168.1.10
Connection reset by peerdepois de algumas tentativas. O OpenSSH do Debian 13 pune temporariamente o IP que acumula falhas de autenticação (recursoPerSourcePenalties) e passa a derrubar as conexões novas dele já na abertura. Isso acontece muito quando o seu computador tem várias chaves carregadas e oferece todas ao servidor. Espere alguns minutos ou reinicie o SSH pelo console do servidor (sudo systemctl restart ssh) e tente de novo com-o IdentitiesOnly=yes.
9.3 Testar o login com a chave¶
O login tem que entrar sem pedir a senha do servidor. Se pedir a passphrase da chave, está certo: ela é local, do seu computador. Se ainda pedir a senha do usuário, a chave não foi instalada. Resolva isso antes de seguir.
9.4 (Recomendado) Criar um atalho no ~/.ssh/config¶
No seu computador, crie uma entrada para o servidor:
cat >> ~/.ssh/config <<'EOF'
Host nginx-lab
HostName 192.168.1.10
User seu_usuario
Port 22
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
A partir daqui, ssh nginx-lab substitui o comando inteiro. Quando a porta mudar no Passo 10, basta trocar a linha Port 22 por Port 2222. Se você deu outro nome à chave, ajuste o IdentityFile.
O IdentitiesOnly yes é o que importa aqui. Sem ele, o cliente SSH oferece ao servidor todas as chaves que tem carregadas, uma por vez. Depois do hardening, o servidor aceita só 3 tentativas (MaxAuthTries 3), e quem tem várias chaves no computador é recusado com Too many authentication failures antes de chegar na chave certa.
Passo 10: Aplicar a configuração de hardening¶
10.1 Criar o arquivo de configuração¶
No Debian 13, o arquivo principal /etc/ssh/sshd_config carrega primeiro tudo que está em /etc/ssh/sshd_config.d/. No SSH, o primeiro valor lido vence. Por isso a configuração vai em um arquivo separado com nome começando em 00: ele é lido antes de qualquer outro e tem prioridade, inclusive sobre arquivos que algumas imagens de cloud colocam ali liberando senha.
Rode no servidor, logado com o usuário que vai administrar o servidor, trocando 2222 pela porta que você escolheu.
O AllowUsers não precisa de edição: o $USER é substituído automaticamente pelo nome do usuário logado no momento em que o comando roda. Isso evita o erro mais comum deste passo, que é deixar um nome de exemplo nessa linha e ter o próprio login recusado, mesmo com a chave certa. O teste do 10.2 mostra o nome que foi gravado.
sudo tee /etc/ssh/sshd_config.d/00-hardening.conf > /dev/null <<EOF
# Porta: 2222 ou a porta que você escolheu
Port 2222
# Root não faz login direto. Entre com seu usuário e use sudo
PermitRootLogin no
# Só chave. Senha desligada
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AuthenticationMethods publickey
# Só estes usuários podem entrar por SSH
AllowUsers $USER
# Limita tentativas e tempo para concluir o login
MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60
# Encerra sessões abandonadas após cerca de 10 minutos sem resposta
ClientAliveInterval 300
ClientAliveCountMax 2
# Recursos que um servidor web não precisa
X11Forwarding no
AllowAgentForwarding no
EOF
O porquê dos itens que mais confundem:
PermitRootLogin no: todo robô tentaroot, porque é o único usuário que existe em qualquer Linux. Bloqueando o root, o atacante precisa adivinhar também o nome do usuário.AuthenticationMethods publickey: garante que nenhum outro método de autenticação seja aceito, mesmo que outro arquivo tente reativar.AllowUsers: lista branca. Qualquer usuário que não estiver aqui é recusado, mesmo com chave válida.MaxStartups 10:30:60: quando há mais de 10 conexões sem login concluído, o SSH começa a recusar novas aleatoriamente (30% de chance), chegando a 100% com 60. Freia ataque em massa sem derrubar o administrador.Port 2222: não é segurança de verdade, porque um scanner encontra a porta. Mas tira o servidor da mira dos robôs que só testam a 22 e limpa o log de lixo, o que facilita enxergar uma tentativa real.
10.2 Validar antes de aplicar¶
Sem saída significa que a configuração está correta. Confira os valores que o SSH vai usar de fato:
sudo sshd -T | grep -Ei '^(port|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|allowusers|authenticationmethods) '
Saída esperada:
port 2222
permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
authenticationmethods publickey
allowusers seu_usuario
Se algum valor estiver diferente, outro arquivo em /etc/ssh/sshd_config.d/ está sendo lido antes. Liste a pasta com ls /etc/ssh/sshd_config.d/ e confira.
10.3 Aplicar¶
Não feche a sessão atual. Ela continua aberta depois do restart e é o seu caminho de volta se algo der errado.
if systemctl is-enabled --quiet ssh.socket 2>/dev/null; then
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket
else
sudo systemctl restart ssh
fi
O SSH pode rodar de dois jeitos no systemd: como serviço (ssh.service) ou ativado por socket (ssh.socket). No modo socket, quem escuta a porta é o systemd, e ele só lê a porta nova depois do daemon-reload. O bloco acima detecta qual modo está ativo e faz o certo para cada um.
10.4 Testar em um terminal novo¶
No seu computador, abra outro terminal:
Tem que entrar só com a chave. Agora confirme que senha foi recusada:
Resultado esperado:
O (publickey) no final é o servidor dizendo que só aceita chave. Com os dois testes certos, a sessão antiga pode ser fechada.
Se o login com a chave der Permission denied (publickey), olhe o log pela sessão que ficou aberta:
A causa mais comum é esta linha:
Ela significa que o AllowUsers ficou com um nome diferente do seu usuário, normalmente o seu_usuario do exemplo copiado sem troca. Nesse caso o servidor recusa o login mesmo com a chave certa, e ainda registra você como invalid user: para quem está de fora, o SSH não revela se o usuário existe ou não. Corrija o nome e reinicie:
sudo sed -i "s/^AllowUsers .*/AllowUsers $USER/" /etc/ssh/sshd_config.d/00-hardening.conf
sudo sshd -t && sudo systemctl restart ssh
Se o login novo continuar falhando, use a sessão que ficou aberta para remover o arquivo e voltar ao estado anterior:
Parte 3: Firewall¶
Passo 11: Configurar o firewall com nftables¶
Até aqui o servidor aceita conexão em qualquer porta. Neste passo você define uma política de bloquear tudo e libera só o que o servidor precisa: SSH e Nginx.
Atenção: uma regra errada aqui bloqueia o seu próximo acesso SSH. A sessão atual continua aberta (a regra
ct state establishedgarante isso), mas não feche o terminal até testar uma nova conexão em outra janela.
11.1 Instalar e habilitar o nftables¶
O serviço nftables carrega o arquivo /etc/nftables.conf a cada boot. Regras criadas só com nft add rule ficam apenas na memória e somem no reboot, por isso as regras vão no arquivo.
11.2 Fazer backup do arquivo original¶
11.3 Escrever as regras¶
Antes de colar, ajuste a porta do SSH. O exemplo usa 2222, mas o valor certo é a porta que você definiu no Passo 10. Para confirmar em qual porta o SSH está escutando:
sudo tee /etc/nftables.conf > /dev/null <<'EOF'
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# Respostas de conexões que o próprio servidor abriu ou já aceitou
ct state established,related accept
ct state invalid drop
# Tráfego interno da própria máquina
iif "lo" accept
# Ping e ICMPv6 (o IPv6 não funciona sem ICMPv6)
meta l4proto { icmp, ipv6-icmp } accept
# SSH: 2222 ou a porta que você escolheu
tcp dport 2222 accept
# Nginx: HTTP e HTTPS liberados para qualquer origem
tcp dport { 80, 443 } accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
EOF
flush ruleset: limpa as regras atuais antes de carregar as novas. Sem isso, as regras seriam somadas às antigas a cada recarga.policy drop: tudo que não bater em nenhuma regraaccepté descartado.ct state established,related accept: aceita o retorno de conexões já existentes. É essa linha que mantém sua sessão SSH viva durante a troca de regras.
11.4 (Opcional) Liberar o Nginx só para uma rede específica¶
Para um front-end interno, como o Zabbix, o Nginx não precisa ficar aberto para a internet. Troque a linha do Nginx no arquivo:
por esta, ajustando o IP ou a rede:
Use uma das duas linhas, nunca as duas. Se a linha aberta para todos continuar no arquivo, a restrição por IP não tem efeito. A mesma lógica vale para o SSH: ip saddr { 192.168.1.0/24 } tcp dport 2222 accept restringe o acesso administrativo à sua rede.
11.5 Validar e aplicar¶
Valide a sintaxe sem aplicar nada:
Sem saída significa que o arquivo está correto. Aplique:
Confira as regras carregadas:
Agora abra outro terminal e teste uma nova conexão SSH (ssh -p 2222 seu_usuario@192.168.1.10). Só feche a sessão original depois que a nova funcionar.
Se algo der errado, restaure o backup pela sessão que continua aberta:
Passo 12: Testar o Nginx a partir de outra máquina¶
Do seu computador, abra o navegador em http://192.168.1.10 ou use o curl:
A resposta tem que ser HTTP/1.1 200 OK. Se funcionou no Passo 8 e falha aqui, o bloqueio está no firewall: revise o Passo 11.
Parte 4: HTTPS com certificado local¶
Para o navegador aceitar uma conexão HTTPS sem aviso, o certificado do site precisa ter sido assinado por uma Autoridade Certificadora (CA) em que o navegador confia. Na internet, essa CA é uma entidade pública, como a Let's Encrypt. Em rede interna, você cria a sua própria CA, emite o certificado do servidor com ela e instala a CA nas máquinas que vão acessar.
O resultado é HTTPS de verdade, com cadeado e sem aviso, sem Cloudflare, sem domínio registrado e sem expor nada para a internet. É o modelo usado em front-ends internos como Zabbix, GLPI e painéis de equipamento.
Para um site público, acessado por qualquer pessoa na internet, o caminho é um certificado da Let's Encrypt. CA própria só funciona em máquinas onde você instalou essa CA.
Passo 13: Criar a CA local¶
Todos os arquivos ficam em /etc/nginx/ssl:
Gere a chave privada da CA:
Crie o arquivo de extensões. São essas extensões que dizem ao navegador que este certificado é uma CA e pode assinar outros:
sudo tee ca.ext > /dev/null <<'EOF'
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, keyCertSign, cRLSign
subjectKeyIdentifier = hash
EOF
CA:TRUE: marca o certificado como autoridade certificadorapathlen:0: esta CA assina certificados de servidor, mas não pode criar outra CA abaixo delakeyCertSign, cRLSign: o único uso permitido da chave é assinar certificados
Gere o certificado da CA, válido por 10 anos:
sudo openssl req -new -key ca.key -subj "/C=BR/O=Example Lab/CN=Example Lab CA" -out ca.csr
sudo openssl x509 -req -in ca.csr -signkey ca.key -days 3650 -sha256 -extfile ca.ext -out ca.crt
O primeiro comando cria o pedido de certificado (.csr) com o nome da CA. O segundo assina esse pedido com a própria chave da CA, por isso ela é chamada de autoassinada: é a raiz da cadeia.
A
ca.keyé a peça mais sensível de todo o processo: quem tem esse arquivo emite certificados que suas máquinas vão aceitar. Em laboratório ela fica no servidor. Em produção, a CA fica em uma máquina separada e só o certificado do servidor vai para o Nginx.
Passo 14: Emitir o certificado do servidor¶
Gere a chave privada do servidor:
Crie o arquivo de extensões, trocando nginx.example.internal pelo nome do seu servidor:
sudo tee server.ext > /dev/null <<'EOF'
basicConstraints = critical, CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
subjectAltName = @alt_names
[alt_names]
DNS.1 = nginx.example.internal
EOF
extendedKeyUsage = serverAuth: o certificado só serve para identificar um servidorsubjectAltName: os nomes que o certificado cobre. Os navegadores só olham este campo para conferir o nome do site. Um certificado sem ele dá erro, mesmo que o nome esteja noCN. Para cobrir mais nomes, adicioneDNS.2,DNS.3e assim por diante.
Crie o pedido e assine com a CA:
sudo openssl req -new -key server.key -subj "/C=BR/O=Example Lab/CN=nginx.example.internal" -out server.csr
sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 397 -sha256 -extfile server.ext -out server.crt
-CAe-CAkey: quem assina é a sua CA, não o próprio servidor-CAcreateserial: cria o arquivoca.srl, que controla o número de série de cada certificado emitido-days 397: alguns sistemas, como os da Apple, recusam certificado de servidor com validade longa, mesmo vindo de CA própria. 397 dias funciona em todos. A CA dura 10 anos, o certificado do servidor é renovado todo ano.
Ajuste as permissões e remova os arquivos temporários:
As chaves privadas ficam legíveis só pelo root. O processo principal do Nginx roda como root e consegue ler, os processos que atendem as requisições não.
Confira se o certificado foi emitido corretamente:
sudo openssl verify -CAfile ca.crt server.crt
openssl x509 -in server.crt -noout -subject -ext subjectAltName -dates
Saída esperada:
server.crt: OK
subject=C=BR, O=Example Lab, CN=nginx.example.internal
X509v3 Subject Alternative Name:
DNS:nginx.example.internal
notBefore=...
notAfter=...
Passo 15: Hardening do Nginx¶
15.1 Configurações globais¶
Os arquivos .conf dentro de /etc/nginx/conf.d/ são carregados dentro do bloco http do nginx.conf. Um arquivo com nome começando em 00 concentra as regras que valem para todos os sites do servidor:
sudo tee /etc/nginx/conf.d/00-hardening.conf > /dev/null <<'EOF'
# Não informa a versão do Nginx nas respostas e páginas de erro
server_tokens off;
# Só TLS 1.2 e 1.3. Versões anteriores têm falhas conhecidas
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# Reaproveita sessões TLS sem reabrir o handshake completo
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
EOF
server_tokens off: o cabeçalho passa deServer: nginx/1.x.xparaServer: nginx. Saber a versão exata ajuda quem procura uma vulnerabilidade específica.ssl_ciphers: só algoritmos com forward secrecy (osECDHE). Se a chave do servidor vazar no futuro, o tráfego gravado antes continua sem poder ser decifrado.ssl_prefer_server_ciphers off: com essa lista, todos os algoritmos são fortes, então o cliente escolhe o mais rápido para o hardware dele.ssl_session_tickets off: os tickets de sessão usam uma chave que raramente é trocada e enfraquecem o forward secrecy. Ossl_session_cachecumpre o mesmo papel sem esse problema.
15.2 Desativar o site padrão¶
O Nginx só carrega arquivos terminados em .conf. Renomear desativa o site padrão sem apagar o arquivo:
15.3 Criar o site com HTTPS¶
Troque nginx.example.internal pelo nome do seu servidor:
sudo tee /etc/nginx/conf.d/example-lab.conf > /dev/null <<'EOF'
# Catch-all: recusa qualquer acesso que não use o nome do site,
# como scanners que batem direto no IP
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on;
return 444;
}
# HTTP: só redireciona para HTTPS
server {
listen 80;
server_name nginx.example.internal;
return 301 https://$host$request_uri;
}
# HTTPS: o site de verdade
server {
listen 443 ssl;
http2 on;
server_name nginx.example.internal;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# Cabeçalhos de segurança
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# add_header Strict-Transport-Security "max-age=31536000" always;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Bloqueia arquivos ocultos como .git e .env
location ~ /\. {
deny all;
}
}
EOF
O porquê de cada bloco:
- Catch-all (
default_server): quem acessa pelo IP ou por um nome qualquer cai aqui. Na porta 80, oreturn 444fecha a conexão sem enviar nenhuma resposta. Na 443, ossl_reject_handshake onrecusa o handshake TLS antes mesmo de mostrar o certificado, então um scanner não descobre nem o nome do seu site. - Redirecionamento: quem digitar
http://é mandado parahttps://com código 301 (redirecionamento permanente). http2 on: ativa o HTTP/2, que carrega várias requisições em paralelo na mesma conexão.X-Content-Type-Options nosniff: impede o navegador de "adivinhar" o tipo de um arquivo e executar como script algo que foi enviado como texto.X-Frame-Options SAMEORIGIN: impede que outro site carregue o seu dentro de umiframe, técnica usada para enganar o usuário a clicar em algo (clickjacking).Referrer-Policy: ao sair do seu site, o navegador informa ao destino só o domínio de origem, não o caminho completo da página.always: envia o cabeçalho também nas respostas de erro (404, 500), não só nas de sucesso.- HSTS (comentado): obriga o navegador a usar HTTPS nesse nome por um ano, sem exceção. Só ative depois que a CA estiver instalada em todas as máquinas que acessam. Com ele ativo, um problema no certificado deixa o site inacessível naquele navegador até a regra expirar.
Os cabeçalhos
add_headerdo blocoserverdeixam de valer em umlocationque tenha o seu próprioadd_header. Se precisar de um cabeçalho específico em umlocation, repita os de segurança dentro dele também.
15.4 Testar e aplicar¶
Saída esperada:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Passo 16: Configurar o computador cliente¶
Para o navegador aceitar o certificado, o computador cliente precisa de duas coisas: saber qual IP corresponde ao nome do servidor e confiar na sua CA.
16.1 Resolver o nome do servidor¶
Em um ambiente com DNS interno, crie um registro A para nginx.example.internal apontando para o servidor. Sem DNS interno, use o arquivo hosts do cliente.
Linux:
Windows (PowerShell como Administrador):
Add-Content -Path "$env:SystemRoot\System32\drivers\etc\hosts" -Value "192.168.1.10 nginx.example.internal"
16.2 Instalar a CA como confiável¶
Copie o certificado da CA (ca.crt, nunca a ca.key) do servidor para o cliente:
Linux (Debian/Ubuntu):
O update-ca-certificates só reconhece arquivos com extensão .crt nessa pasta. A saída deve informar 1 added.
Linux (Fedora, RHEL, Rocky, AlmaLinux):
A família Red Hat usa outra pasta e outro comando, mas o resultado é o mesmo: a CA passa a valer para o sistema inteiro. No Fedora, o Firefox e o Chrome também leem essa loja, então os navegadores passam a confiar na CA junto com o curl.
Windows (PowerShell como Administrador):
No Windows, o Chrome e o Edge usam o repositório de certificados do sistema e passam a confiar na CA imediatamente. Feche e abra o navegador.
Navegador e terminal não usam necessariamente a mesma loja de certificados. O curl sempre lê a loja do sistema. Já os navegadores, dependendo da distribuição, podem ter loja própria: no Debian e no Ubuntu é comum o Firefox e o Chrome não enxergarem a CA instalada no sistema. Se o navegador continuar mostrando aviso, importe a CA pelo próprio navegador. No Firefox, o caminho é Configurações > Privacidade e Segurança > Certificados > Ver certificados > Autoridades > Importar.
O contrário também acontece: a página abrir no navegador e o curl falhar com unable to get local issuer certificate. Nesse caso, a CA está só no navegador, ou você aceitou o aviso de risco na tela e o navegador criou uma exceção. Instale a CA no sistema com os comandos acima.
Passo 17: Validar o HTTPS¶
Do computador cliente:
Saída esperada:
HTTP/2 200
server: nginx
content-type: text/html
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
Pontos para conferir: HTTP/2, server: nginx sem versão e os três cabeçalhos de segurança. O curl não reclamou do certificado, o que confirma que a CA foi instalada corretamente.
Teste o redirecionamento:
Teste o acesso direto pelo IP, que agora precisa ser recusado:
O primeiro retorna Empty reply from server (o return 444). O segundo retorna o erro abaixo, gerado pelo ssl_reject_handshake:
O unrecognized name é o servidor dizendo que não reconhece o nome pedido no handshake. Acessando pelo IP, nenhum nome é enviado, então o Nginx encerra a conexão antes de mostrar o certificado. O teste do Passo 12 deixa de funcionar a partir daqui, e esse é o comportamento esperado.
Por último, abra https://nginx.example.internal no navegador: o cadeado aparece sem nenhum aviso.
Onde ficam os arquivos¶
O pacote do nginx.org usa uma estrutura diferente do pacote do Debian. Não existem as pastas sites-available e sites-enabled: cada site é um arquivo .conf dentro de conf.d.
| Caminho | O que é |
|---|---|
/etc/nginx/nginx.conf |
Configuração global do serviço |
/etc/nginx/conf.d/ |
Um arquivo .conf por site. Todo .conf aqui é carregado automaticamente |
/etc/nginx/conf.d/00-hardening.conf |
Hardening global: versão oculta e parâmetros de TLS |
/etc/nginx/conf.d/example-lab.conf |
Site com catch-all, redirecionamento e HTTPS |
/etc/nginx/ssl/ca.crt e ca.key |
Certificado e chave da CA local |
/etc/nginx/ssl/server.crt e server.key |
Certificado e chave do servidor |
/usr/share/nginx/html/ |
Arquivos da página padrão |
/var/log/nginx/access.log |
Registro de cada requisição recebida |
/var/log/nginx/error.log |
Erros do serviço. Primeiro lugar a olhar quando algo não funciona |
/etc/ssh/sshd_config.d/00-hardening.conf |
Hardening do SSH |
/etc/nftables.conf |
Regras do firewall |
Operação do dia a dia¶
Sempre que editar qualquer arquivo de configuração, teste antes de aplicar:
| Comando | Quando usar |
|---|---|
sudo systemctl reload nginx |
Depois de editar a configuração. Relê os arquivos sem derrubar as conexões abertas |
sudo systemctl restart nginx |
Quando o reload não resolve ou o serviço travou. Encerra e inicia o processo, derrubando as conexões em andamento |
systemctl status nginx |
Ver se o serviço está rodando e as últimas mensagens de log |
sudo journalctl -u nginx -n 50 |
Ver as últimas 50 linhas de log do serviço no systemd |
nginx -v |
Ver a versão instalada |
sudo sshd -t |
Validar a configuração do SSH antes de reiniciar |
sudo nft -c -f /etc/nftables.conf |
Validar as regras do firewall antes de aplicar |
O nginx -t antes do reload não é opcional. Se a configuração tiver erro, o reload falha e o Nginx continua com a configuração antiga, mas um restart com configuração quebrada derruba o serviço e ele não volta.
Renovar o certificado do servidor¶
O certificado do servidor vale 397 dias. Para conferir a data de vencimento:
Para renovar, repita o Passo 14 a partir do pedido de certificado (openssl req -new -key server.key ...) e rode sudo nginx -t && sudo systemctl reload nginx. A CA continua a mesma, então os clientes não precisam de nenhuma alteração.
Referências oficiais: