Filosofia: processo vs evento
O Apache2 e o NGINX resolvem o mesmo problema de formas radicalmente diferentes. Entender essa diferença é o primeiro passo para migrar sem dor.
Modelo orientado a PROCESSO Cada requisição = 1 processo (ou thread, com mpm_worker) Simples de configurar. Suporte a .htaccess por pasta. Módulos carregados em memória. Gargalo: muitas conexões simultâneas consomem RAM.
Modelo orientado a EVENTO 1 worker = N conexões (loop de eventos assíncrono) Config centralizada. Sem .htaccess (por design). Módulos compilados no binário. Eficiente: 10k+ conexões com baixíssimo uso de memória.
Quando o NGINX ganha do Apache?
- Sites com alto volume de acessos simultâneos
- Servir arquivos estáticos (imagens, CSS, JS) em escala
- Proxy reverso para múltiplos backends (PHP-FPM, Node, Python)
- Load balancing e terminação SSL
- Ambientes com RAM limitada (VPS pequenos)
Instalação no Ubuntu/Debian
Parar e desabilitar o Apache2
Instalar o NGINX
Habilitar e iniciar
Verificar se está rodando
Acesse http://localhost — você deve ver a página padrão do NGINX.
Estrutura de arquivos
/etc/apache2/ apache2.conf # config global ports.conf # portas sites-available/ # vhosts inativos sites-enabled/ # vhosts ativos mods-available/ # módulos conf-available/ # configs extras
/etc/nginx/ nginx.conf # config global # (sem ports.conf separado) sites-available/ # blocos inativos sites-enabled/ # blocos ativos conf.d/ # configs extras # (módulos compilados no bin)
Anatomia do nginx.conf
# Contexto: global user www-data; worker_processes auto; # = nº de CPUs events { worker_connections 1024; # conexões por worker } # Contexto: http http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # Inclui todos os sites ativos include /etc/nginx/sites-enabled/*; }
global → events → http → server → location. Diretivas de um contexto são herdadas pelos filhos, mas podem ser sobrescritas.
Ativar/desativar sites (igual ao Apache)
Virtual Hosts vs Server Blocks
O que o Apache chama de VirtualHost, o NGINX chama de server block. A lógica é idêntica; a sintaxe muda.
Site estático simples
<VirtualHost *:80> ServerName meusite.com ServerAlias www.meusite.com DocumentRoot /var/www/meusite <Directory /var/www/meusite> AllowOverride All Require all granted </Directory> ErrorLog ${APACHE_LOG_DIR}/erro.log CustomLog ${APACHE_LOG_DIR}/acesso.log combined </VirtualHost>
server { listen 80; server_name meusite.com www.meusite.com; root /var/www/meusite; index index.html index.php; location / { try_files $uri $uri/ =404; } error_log /var/log/nginx/erro.log; access_log /var/log/nginx/acesso.log; }
Site PHP com PHP-FPM (substitui mod_php)
sudo apt install php8.3-fpm
<VirtualHost *:80> ServerName app.local DocumentRoot /var/www/app/public # PHP roda dentro do Apache # mod_php cuida disso <Directory /var/www/app/public> AllowOverride All Require all granted </Directory> </VirtualHost>
server { listen 80; server_name app.local; root /var/www/app/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; } }
Sem .htaccess — e isso é bom
O NGINX não lê arquivos .htaccess. Isso é intencional: ler e interpretar um arquivo por diretório a cada requisição tem custo de I/O. No NGINX, toda a configuração fica nos server blocks.
.htaccess do seu projeto serão ignorados silenciosamente. Você precisa traduzir as regras para a sintaxe do NGINX nos server blocks.
Tabela de equivalências .htaccess → NGINX
| Apache .htaccess | NGINX (location block) | Descrição |
|---|---|---|
| Options -Indexes | autoindex off; | Desabilitar listagem de diretório |
| Options +Indexes | autoindex on; | Habilitar listagem de diretório |
| DirectoryIndex | index | Arquivo padrão do diretório |
| ErrorDocument 404 | error_page 404 | Página de erro customizada |
| AllowOverride All | N/A | Conceito não existe no NGINX |
| Require all denied | deny all; | Bloquear acesso ao diretório |
| Require ip 192.168 | allow 192.168.0.0/16; deny all; | Controle de acesso por IP |
| Header set X-Frame | add_header X-Frame-Options | Cabeçalhos HTTP customizados |
| ExpiresActive | expires / add_header Cache-Control | Cache de arquivos estáticos |
Cache de estáticos — exemplo completo
ExpiresActive On ExpiresByType image/png "access plus 1 month" ExpiresByType text/css "access plus 1 week" ExpiresByType application/javascript "access plus 1 week"
location ~* \.(png|jpg|gif|ico|webp)$ { expires 30d; add_header Cache-Control "public"; } location ~* \.(css|js)$ { expires 7d; add_header Cache-Control "public"; }
Rewrites: mod_rewrite → try_files / rewrite
A maioria dos RewriteRule do Apache se traduz em try_files ou rewrite no NGINX — geralmente com menos linhas.
Front controller (Laravel, Symfony, CodeIgniter…)
RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [L]
location / { try_files $uri $uri/ /index.php?$query_string; }
try_files $uri $uri/ /index.php?$query_string significa: tenta o arquivo → tenta como diretório → redireciona para index.php mantendo a query string.
Redirect 301 de domínio
RewriteEngine On RewriteCond %{HTTP_HOST} ^www\. [NC] RewriteRule ^(.*)$ https://meusite.com/$1 [R=301,L]
server { listen 80; server_name www.meusite.com; return 301 https://meusite.com$request_uri; }
Redirect HTTP → HTTPS
RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
server { listen 80; server_name meusite.com www.meusite.com; return 301 https://$host$request_uri; }
Rewrite com regex (quando try_files não basta)
location /old-path/ { rewrite ^/old-path/(.*)$ /new-path/$1 permanent; } # Captura grupos com $1, $2... igual ao Apache # "permanent" = 301, "redirect" = 302
Proxy Reverso
O NGINX foi feito para isso. Enquanto no Apache você precisa do mod_proxy, no NGINX é nativo e muito mais performático.
Proxy para Node.js / Python / qualquer app na porta local
ProxyPreserveHost On ProxyPass / http://127.0.0.1:3000/ ProxyPassReverse / http://127.0.0.1:3000/
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_cache_bypass $http_upgrade; }
Load Balancer simples
upstream backend { server 127.0.0.1:3001; server 127.0.0.1:3002; server 127.0.0.1:3003; } server { listen 80; server_name meusite.com; location / { proxy_pass http://backend; # round-robin por padrão } }
upstream: least_conn; (menos conexões) ou ip_hash; (sticky session por IP). Round-robin é o padrão sem declaração.
SSL / TLS com Certbot
Instalar Certbot para NGINX
O Certbot modifica automaticamente seu server block. O resultado será algo assim:
server { listen 80; server_name meusite.com www.meusite.com; return 301 https://$host$request_uri; # redirect automático } server { listen 443 ssl http2; server_name meusite.com www.meusite.com; root /var/www/meusite; index index.php index.html; # Gerenciado pelo Certbot: 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; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; } }
Renovação automática
O Certbot já instala um cron/timer systemd. Apenas verifique que está ativo.
Comandos essenciais
| Apache2 | NGINX | O que faz |
|---|---|---|
| apachectl -t | nginx -t | Testar configuração sem reiniciar |
| apachectl graceful | nginx -s reload | Recarregar sem derrubar conexões |
| service apache2 restart | systemctl restart nginx | Reiniciar o servidor |
| service apache2 stop | systemctl stop nginx | Parar o servidor |
| a2ensite meusite | ln -s sites-available/meusite sites-enabled/ | Ativar site |
| a2dissite meusite | rm sites-enabled/meusite | Desativar site |
| a2enmod rewrite | Compilado | Módulos já estão no binário |
| tail -f /var/log/apache2/error.log | tail -f /var/log/nginx/error.log | Ver log de erros em tempo real |
| tail -f /var/log/apache2/access.log | tail -f /var/log/nginx/access.log | Ver log de acessos em tempo real |
Fluxo de trabalho recomendado
# 1. Edite sua config sudo nano /etc/nginx/sites-available/meusite # 2. SEMPRE teste antes de aplicar sudo nginx -t # 3. Recarregue sem downtime sudo systemctl reload nginx # 4. Veja se deu erro sudo journalctl -u nginx -f
Tabela geral de equivalências
| Conceito | Apache2 | NGINX | Disponível? |
|---|---|---|---|
| Configuração | |||
| Config global | apache2.conf | nginx.conf | OK |
| Config por diretório | .htaccess | Não existe | Traduzir |
| VirtualHost | <VirtualHost> | server { } | OK |
| PHP | |||
| Executar PHP | mod_php (embutido) | PHP-FPM (externo) | OK |
| Socket PHP | automático | fastcgi_pass unix:/run/php/phpX.X-fpm.sock | OK |
| Módulos | |||
| mod_rewrite | RewriteRule | try_files / rewrite | OK |
| mod_proxy | ProxyPass | proxy_pass | OK |
| mod_ssl | SSLEngine on | listen 443 ssl | OK |
| mod_deflate | AddOutputFilterByType DEFLATE | gzip on; gzip_types … | OK |
| mod_headers | Header set X-… | add_header X-… | OK |
| mod_expires | ExpiresActive On | expires 30d | OK |
| mod_status | /server-status | stub_status | OK |
| mod_userdir | UserDir public_html | Manual | Configurar |
Compressão GZIP
AddOutputFilterByType DEFLATE text/html text/css application/javascript
gzip on; gzip_vary on; gzip_types text/html text/css application/javascript application/json; gzip_comp_level 5;
Bloquear acesso a arquivos sensíveis
<FilesMatch "^\.env$|\.sqlite$"> Require all denied </FilesMatch>
location ~ /\.(env|sqlite|git) { deny all; return 404; }
- Instalar PHP-FPM (
php8.x-fpm) e configurar o socket - Traduzir todos os
.htaccesspara server blocks - Substituir
RewriteRuleportry_filesourewrite - Recriar redirects 301/302 como blocos
server { return … } - Ajustar permissões: usuário
www-dataprecisa ler os arquivos - Rodar
nginx -tantes de qualquer reload - Monitorar
/var/log/nginx/error.lognos primeiros dias