Por volta de meados de 2024, depois do quinto servidor naquele mês onde esquecemos de configurar o DKIM e passamos uma hora tentando entender por que os e-mails de confirmação estavam voltando, decidimos escrever um script de deploy. Um de verdade. Não um arquivo bash com 40 linhas e uma oração, mas um sistema em Python que pega um VPS com Ubuntu limpo e transforma em um servidor web pronto para produção em cerca de dez minutos. Com e-mail. Com SSL. Com regras de firewall. Com backups. Com um health check que detecta erros de configuração antes de enviar qualquer tráfego.

Seis meses e aproximadamente 7.000 linhas de código depois, esse script faz o deploy de cada servidor que operamos em 9 mercados LATAM para nossa operação de media buying. Este artigo explica por que construímos, o que ele faz e o que aprendemos sobre configuração de servidores que a maioria das equipes — seja em media buying, SEO ou tráfego pago — nunca pensa até que algo quebra às 2 da manhã de uma terça-feira.

O Custo Real de Configurar Servidores Manualmente no Media Buying

Nossa operação roda ativos digitais próprios: landing pages, content hubs, sites de comparação. Cada um vive no seu próprio VPS com seu próprio domínio, seu próprio certificado SSL, sua própria configuração de e-mail. Quando você gerencia um punhado deles, a configuração manual é chata mas sobrevivível. Quando você opera dezenas em múltiplos países, vira o gargalo que atrasa todo o resto.

Rastreamos nosso processo de deploy manual durante um mês. Os números não foram bonitos.

Levar um servidor de um VPS limpo até pronto para produção tomava de dois a três dias de trabalho. Não dias de digitação ininterrupta — a checklist em si cobria instalar pacotes, configurar nginx, configurar PHP-FPM, obter SSL, configurar Postfix com DKIM, configurar acesso para transferência de arquivos, configurar o firewall e rodar verificações básicas. Mas entre esses passos estavam as esperas que ninguém planeja: propagação de DNS, novas tentativas de certificado, testes de entregabilidade de e-mail e a constante troca de contexto de um operador rodando a mesma sequência de comandos com variações menores em cada servidor.

Taxa de erro em deploys manuais: aproximadamente 1 em cada 4 tinha algo mal configurado. Registro DKIM faltando. Limite de memória PHP errado. Regra de firewall que bloqueava a porta de e-mail. Certificado SSL solicitado antes do DNS propagar, então o certbot falhou silenciosamente e ninguém notou até uma semana depois quando o certificado temporário expirou. Cada erro custava de uma hora até mais um dia inteiro para diagnosticar e corrigir.

Somando tudo, cada entrada em um novo mercado ou rotação de servidores virava um projeto de infraestrutura de vários dias. Para uma equipe pequena de media buying, isso significava uma parte considerável do mês de uma pessoa indo para trabalho de configuração repetitivo em vez de otimização de campanhas, criação de conteúdo ou pesquisa de mercado.

A pior parte não era o tempo. Era a inconsistência. Dois servidores configurados pela mesma pessoa com uma semana de diferença tinham configurações PHP diferentes, regras de firewall diferentes, configurações de e-mail diferentes. Quando algo dava errado em um servidor, o processo de debugging era único para aquele servidor porque a configuração era única. Nenhum servidor era igual ao outro.

O Que Construímos: Um Script, 40 Pacotes, 10 Minutos

O sistema é um único script Python usando a biblioteca Fabric para automação SSH. Um bloco de configuração no topo — IP do servidor, domínio, credenciais do provedor DNS, algumas flags de funcionalidade — e um único comando para rodá-lo. O script conecta no VPS via SSH, instala tudo, configura tudo, testa tudo e produz um relatório de deploy com todas as credenciais e detalhes de conexão.

Aqui está o que roda, em ordem, em cada deploy:

Preparação do sistema. Atualização do SO, configuração do hostname baseado no domínio, instalação de pacotes. Instalamos aproximadamente 40 pacotes em um único comando apt — nginx, PHP-FPM com a versão específica que escolhemos, Postfix, OpenDKIM, certbot, fail2ban, UFW e todas as extensões PHP que um site moderno precisa. O script preconfigura o Postfix através do debconf antes da instalação para evitar os prompts interativos que quebram instalações desatendidas. Após a instalação, o cache de pacotes é limpo automaticamente — economizando 200 a 500 MB de espaço em disco em instâncias VPS pequenas.

PHP-FPM com auto-tuning. O script lê a RAM total do servidor e calcula a quantidade ideal de workers. Um VPS de 512 MB recebe 5 workers. Um de 1 GB recebe 10. Um de 4 GB recebe 50. Limite de memória, OPcache, limites de upload, timeouts de execução — tudo configurado condicionalmente com base no tipo de site: PHP estático ou WordPress. O pool roda sob um nome gerado com seu próprio socket, não o pool www padrão.

SFTP para gerenciamento de arquivos. Abandonamos o FTP tradicional completamente. SFTP via SSH significa um serviço a menos rodando, uma porta a menos aberta e transferência de arquivos criptografada por padrão. O script cria um usuário dedicado com o diretório web como home, gera uma senha forte e produz um arquivo de configuração XML compatível com FileZilla que importamos com um clique.

A sequência completa — da conexão SSH ao relatório de deploy — se completa em cerca de 10 minutos, e a maior parte da variação depende da velocidade do mirror de pacotes do provedor VPS.

DNS via API e SSL que Não Quebra

Configurar DNS manualmente significa entrar no painel do registrador, criar registros A, registros MX, registros TXT SPF, e depois voltar para adicionar registros DKIM e DMARC após o servidor de e-mail gerar suas chaves. Cada um desses passos é uma oportunidade para erros de copy-paste. Um caractere errado em um registro TXT DKIM e seu e-mail de saída falha na verificação DKIM — silenciosamente.

O script suporta três modos de DNS: API do Cloudflare, API da Namecheap e BIND9 local. Para Cloudflare e Namecheap, ele cria todos os registros automaticamente — registro A apontando para o IP do servidor, registro MX para e-mail, registro SPF, e depois registros DKIM e DMARC. Sem logins em painéis. Sem copiar e colar valores TXT. O script lê a chave pública DKIM gerada e a envia diretamente para a API DNS.

Certificados SSL — Let's Encrypt ou ZeroSSL — são obtidos com lógica de retry e verificações de propagação DNS. Antes de solicitar o certificado, o script verifica se o domínio realmente resolve para o IP do servidor consultando Google DNS, Cloudflare DNS e Quad9. Se o DNS ainda não propagou, o script espera e tenta novamente em vez de deixar o certbot falhar com um erro críptico. O timer de auto-renovação é configurado automaticamente.

Só isso evitou mais deploys falhados do que qualquer outra funcionalidade individual. No nosso processo manual, cerca de 30% das falhas de SSL eram causadas por solicitar o certificado antes do DNS ter propagado — um problema que simplesmente desaparece quando o script verifica primeiro.

E-mail que Passa em Todos os Filtros de Caixa de Entrada

Fazer o e-mail funcionar é fácil. Fazer o e-mail realmente chegar nas caixas de entrada — isso exige quatro coisas configuradas corretamente. A maioria dos tutoriais de deploy cobre no máximo duas.

Postfix cuida do envio e recebimento. O script escreve o main.cf inteiro a partir de um template com o domínio correto, hostname, certificados TLS e restrições de relay. Também adiciona regras anti-spam: validação HELO, verificação do domínio do remetente, verificações de destinatário. Essas quatro linhas rejeitam aproximadamente 40% do lixo antes do corpo da mensagem ser transferido. O tamanho da caixa de e-mail é limitado a 500 MB por conta para prevenir estouro de disco por inundações de spam.

Assinatura DKIM via OpenDKIM. O script gera um par de chaves RSA de 2048 bits, configura as tabelas de assinatura e envia a chave pública para o DNS automaticamente. Cada e-mail de saída recebe uma assinatura criptográfica que os servidores receptores podem verificar. Sem DKIM, Gmail e Outlook mandam suas mensagens direto para o spam.

Registros SPF e DMARC vão para o DNS junto com o DKIM. SPF diz aos servidores receptores quais IPs estão autorizados a enviar e-mail pelo domínio. DMARC diz o que fazer quando SPF ou DKIM falham. Junto com DKIM, formam o trio de autenticação que a infraestrutura de e-mail moderna exige.

Dovecot IMAP com portas de submission permite enviar e receber e-mail através de um cliente padrão como Thunderbird. O script configura IMAPS na porta 993, submission na porta 587 com STARTTLS e SMTPS na porta 465. Gera um arquivo XML de autoconfig para o Thunderbird para que o cliente de e-mail configure tudo automaticamente — basta inserir e-mail e senha.

Após a configuração, o script testa a entrega de e-mail para cada caixa e verifica se as mensagens chegam no Maildir. Também verifica se a porta 25 de saída está aberta — muitos provedores cloud a bloqueiam por padrão para prevenir spam. Se estiver bloqueada, o relatório de deploy inclui uma solicitação pronta para enviar ao provedor de hosting pedindo o desbloqueio.

Nginx e PHP: A Configuração que Ninguém Comenta

A maioria dos guias de deploy para em instalar nginx e PHP, talvez configurar o limite de memória. Existem várias configurações de produção que importam tanto para segurança quanto para estabilidade e que raramente são mencionadas em tutoriais.

Configuração de produção do Nginx. Não a padrão do Ubuntu com server_tokens ativados e sem headers de segurança. Nossa configuração inclui redirecionamento HTTPS com HSTS, HTTP/2, X-Frame-Options, X-Content-Type-Options, compressão gzip ou brotli, e um bloco de servidor catch-all que rejeita requisições feitas diretamente ao IP em vez do domínio. A configuração SSL inclui session tickets, parâmetros DH e suítes de cifras modernas para TLS 1.2 e 1.3. O caminho de auto-renovação do certbot está explicitamente na whitelist para que a renovação do certificado não quebre com a regra de negação de dotfiles.

PHP open_basedir restringe quais diretórios o PHP pode acessar. Limitamos ao diretório web, /tmp e o caminho de bibliotecas do sistema PHP. Se alguém explorar uma vulnerabilidade em um script PHP, não consegue ler arquivos do sistema como /etc/passwd, configurações do servidor de e-mail ou chaves SSH. Uma linha na configuração do pool FPM, zero custo de performance, redução significativa do raio de dano.

disable_functions bloqueia funções PHP perigosas. Para sites estáticos: exec, system, shell_exec, passthru, proc_open e popen ficam desabilitadas. Para deploys WordPress: proc_open e popen ficam habilitadas porque WP-CLI e vários plugins precisam delas. O restante fica bloqueado. Isso previne que um script PHP comprometido execute comandos do sistema.

Isolamento de sessões. As sessões PHP vão para um diretório por pool em /var/lib/php/sessions/ com permissões 700. Cada site tem seu próprio armazenamento de sessões. Vazamento de sessões entre sites é impossível mesmo que múltiplos sites compartilhem o servidor.

Limites de upload e execução são configurados diferentemente dependendo do tipo de site. WordPress recebe 128 MB de limite de upload, 120 segundos de timeout de execução e 5000 max_input_vars (menus WordPress com muitos itens precisam disso). Sites PHP estáticos recebem 32 MB de upload, 30 segundos de timeout e 1000 max_input_vars — limites mais rígidos significam superfície de ataque menor.

OPcache recebe 128 MB de memória compartilhada, valida timestamps de arquivos a cada 2 segundos em vez de cada requisição, e faz cache de até 10.000 arquivos. O cache de realpath é configurado para 4 MB em vez do padrão do PHP de 4 KB — essa mudança sozinha reduz o overhead de include/require de forma mensurável em sites WordPress com dezenas de plugins.

Firewall, Fail2ban e Isolamento de Acesso a Arquivos

O firewall UFW começa com uma política de negar todo tráfego de entrada. Somente as portas realmente necessárias são abertas: SSH, HTTP, HTTPS, SMTP. Se o stack completo de e-mail estiver habilitado, submission (587), SMTPS (465) e IMAPS (993) são adicionadas. Se usar FTP tradicional em vez de SFTP, a porta 21 e o range passivo são abertos. Nada mais. O firewall é ativado cedo no deploy — antes da maioria dos serviços ser instalada — para que o servidor nunca fique completamente aberto durante a configuração.

Fail2ban monitora logs de autenticação e bloqueia automaticamente IPs após tentativas de login falhas. Jails são configuradas para SSH, autenticação HTTP do nginx e detecção de bots do nginx. Quando o stack completo de e-mail está habilitado, jails adicionais são ativadas para Dovecot (força bruta IMAP) e Postfix SASL (força bruta de autenticação SMTP). Logs rotacionam semanalmente com retenção de 7 dias.

Os limites de descritores de arquivo são elevados para 65.535 tanto para nginx quanto para PHP-FPM através de overrides de serviços systemd. O padrão do Ubuntu de 1.024 é suficiente para tráfego leve mas causa erros "too many open files" sob carga — um problema extremamente chato de debugar quando acontece em escala porque o erro é intermitente e depende do número de conexões concorrentes.

O swap é dimensionado automaticamente baseado na RAM do servidor (2x para servidores abaixo de 1 GB, 1x para 2 a 4 GB, com teto de 4 GB para servidores maiores). O swappiness é configurado para 15 em vez do padrão do Ubuntu de 60, o que significa que o kernel mantém os workers de PHP-FPM na RAM em vez de mandá-los para o swap quando o uso de memória atinge 40%.

36 Verificações Automatizadas Antes de Enviar Tráfego

O deploy termina com um health check automatizado que verifica cada componente. Não apenas "o nginx iniciou?" — verificação funcional do comportamento real.

Nginx: rodando e a sintaxe de configuração passa no nginx -t. PHP-FPM: rodando e o arquivo de socket existe. Requisição HTTP para localhost com o header Host correto retorna 200. Requisição HTTPS para o domínio real verifica o certificado SSL. Postfix: rodando e postfix check valida a configuração de e-mail e a porta 25 está escutando. Registros DKIM, SPF e DMARC consultados via Google DNS para confirmar que são visíveis globalmente.

Para SFTP: o subsistema SSH está habilitado, o usuário existe com o diretório home correto, e — quando o sshpass está disponível — um login SFTP real e listagem de diretório são realizados.

Swap ativo, swappiness em 15, limites de descritores de arquivo do nginx em 65.535 (lidos de /proc, não da configuração — o valor real em runtime), cache apt limpo, timer de renovação do certbot ativo, atualizações de segurança desatendidas habilitadas, fail2ban rodando, backups automáticos agendados.

Quando o stack completo de e-mail está ativado: Dovecot rodando, porta 993 escutando, portas 587 e 465 escutando, autoconfig do Thunderbird acessível via HTTPS.

Se qualquer coisa falhar, o relatório marca com a verificação específica que falhou e qual era o valor esperado. Já detectamos registros DNS mal configurados, emissão de SSL falhada, pools PHP-FPM mortos e serviços de e-mail sem resposta — tudo antes de um único visitante chegar ao servidor. Encontrar esses problemas durante o deploy leva segundos. Encontrá-los depois que o tráfego está rodando custa horas e conversões perdidas.

Mais de um Ano de Deploys Automatizados: Os Números

Estamos rodando o sistema automatizado desde o final de 2024. A comparação com nosso processo manual:

O tempo de deploy caiu de dois a três dias de trabalho para cerca de 10 minutos. O passo individual mais longo é a instalação de pacotes, que depende da velocidade do mirror do provedor VPS. Todo o resto — DNS, SSL, e-mail, nginx, PHP, firewall, health check — roda em menos de 5 minutos combinados.

A taxa de erro foi de 25% para efetivamente zero. O health check pega tudo. Não tivemos um incidente de "esqueci o DKIM" ou "versão PHP errada" desde que começamos a usar o script. Quando erros ocorrem, são ambientais — porta 25 de saída bloqueada pelo provedor, propagação DNS mais lenta que o esperado — e o script reporta claramente em vez de falhar silenciosamente.

A rotação de servidores virou trivial. Quando precisamos mover um site para um novo IP, o processo é: subir um novo VPS, rodar o script, fazer upload do site via SFTP, trocar o DNS. Menos de 30 minutos no total. Antes da automação, isso era um projeto de vários dias que ninguém queria começar.

O script também gera um relatório de deploy completo com cada credencial, endereço IP, caminho de arquivo e parâmetro de configuração. Acabou a busca no histórico do terminal para encontrar a senha FTP configurada três semanas atrás. Cada servidor tem um arquivo de relatório com timestamp com tudo que é necessário para gerenciá-lo.

O investimento — algumas centenas de horas de desenvolvimento ao longo de vários meses — se pagou dentro do primeiro mês de uso regular. Não por alguma métrica abstrata de eficiência, mas por horas concretas recuperadas e erros concretos eliminados. Cada servidor que fazemos deploy parte da mesma base blindada, testada e documentada. Sem exceções. Sem atalhos. Sem "vou configurar o firewall depois."