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 menos de 20 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.

Oito 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.

Tempo médio para fazer deploy de um servidor do zero: 2 horas e 40 minutos. Isso incluía 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. Duas horas e quarenta minutos do tempo de um operador, fazendo 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 outros 30 a 60 minutos para diagnosticar e corrigir.

Essa conta dá cerca de 15 horas por mês só em configuração de servidores. Para uma equipe pequena de media buying, isso é um quarto completo do tempo de trabalho de uma pessoa gasto em trabalho de infraestrutura 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, 15 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 14 a 18 minutos dependendo 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.

Seis Meses 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 2 horas e 40 minutos para 14 a 18 minutos. A parte mais longa é 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 meio dia 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."