Dia 15: Apache e Nginx — Web Server e Virtual Hosts

Apache e Nginx são os dois web servers mais utilizados em Linux, com arquitecturas diferentes que se complementam. Apache usa um modelo baseado em processos ou threads — cada ligação ocupa um processo ou thread dedicado. Nginx usa um modelo assíncrono orientado a eventos — um único processo gere milhares de ligações simultâneas. A combinação mais comum em produção é Nginx como reverse proxy à frente de Apache: Nginx serve conteúdo estático e faz load balancing, Apache processa PHP e conteúdo dinâmico. Este artigo mostra como instalar ambos, configurar virtual hosts, montar um reverse proxy, activar HTTPS com Let’s Encrypt e implementar load balancing.

Atenção: Nunca ter Apache e Nginx a escutar na mesma porta (80 ou 443) em simultâneo. Conflitos de portas causam falhas de arranque silenciosas — um serviço arranca e o outro fica parado sem erro evidente.

Apache vs Nginx — Quando Usar Cada Um

A escolha entre Apache e Nginx depende do tipo de conteúdo a servir. Apache é mais antigo e tem suporte nativo para .htaccess, directivas por directório e mod_php. Nginx é mais rápido a servir ficheiros estáticos (imagens, CSS, JS) e foi desenhado desde o início para funcionar como reverse proxy e load balancer. A documentação oficial de ambos cobre estes cenários em detalhe (Apache HTTP Server Docs, Nginx Beginner’s Guide).

O modelo de processamento explica a diferença de performance. No Apache, o MPM (Multi-Processing Module) prefork cria um processo por ligação — eficaz mas pesado em memória. O MPM worker usa threads, mais leve. O Nginx usa um modelo event-driven: um processo master gere processos worker, e cada worker processa ligações de forma assíncrona via epoll no Linux. Isto permite a um único worker gerir centenas de ligações sem bloquear.

Característica Apache Nginx
Modelo Processo/Thread por ligação Assíncrono, orientado a eventos
Conteúdo estático Rápido Mais rápido (até 2-3x)
Conteúdo dinâmico (PHP) Nativo (mod_php) Via PHP-FPM (externo)
.htaccess Suportado Não suportado
Reverse Proxy Via mod_proxy Nativo e optimizado
Load Balancing Via mod_proxy_balancer Nativo (upstream)
Configuração por directório Sim (.htaccess) Não (apenas config global)

Em produção, o padrão mais comum é Nginx à frente: recebe as ligações na porta 80/443, serve ficheiros estáticos directamente e faz proxy do conteúdo dinâmico para Apache (ou PHP-FPM) num backend. Esta arquitectura tira o melhor dos dois — velocidade de Nginx para estático, flexibilidade de Apache para dinâmico.

Pré-requisitos e Instalação

Antes de instalar, confirmar que a porta 80 está livre. Se já existir um web server activo, há que pará-lo primeiro ou usar uma porta diferente. O comando ss mostra o que está a escutar (coberto em detalhe no Dia 8 deste curso).

O comando seguinte verifica se a porta 80 está ocupada antes de instalar. Se retornar resultado, algo já está a escutar e há que resolver o conflito antes de avançar.

ss -tlnp | grep ':80 '

A instalação difere entre Debian/Ubuntu e RHEL/Rocky/Alma. No Debian, o pacote chama-se apache2 e o binário é apache2. No RHEL, o pacote chama-se httpd e o binário é httpd. Para Nginx, o nome é igual em ambas as famílias.

# Ubuntu / Debian
sudo apt update
sudo apt install apache2 nginx

# RHEL / Rocky / AlmaLinux
sudo dnf install httpd nginx

Depois de instalar, activar e iniciar os serviços. O enable garante que o serviço arranca automaticamente após reinício. O systemctl foi coberto em detalhe no Dia 7 deste curso.

# Ubuntu / Debian — Apache
sudo systemctl enable apache2
sudo systemctl start apache2

# RHEL / Rocky / AlmaLinux — Apache
sudo systemctl enable httpd
sudo systemctl start httpd

# Nginx (ambas as distribuições)
sudo systemctl enable nginx
sudo systemctl start nginx

A firewall tem de permitir tráfego HTTP (porta 80) e HTTPS (porta 443). No Ubuntu usa-se UFW; no RHEL usa-se firewalld. Ambos foram abordados no Dia 9.

# Ubuntu / Debian — UFW
sudo ufw allow 'Apache Full'
sudo ufw allow 'Nginx Full'

# RHEL / Rocky / AlmaLinux — firewalld
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

Neste ponto, abrir o navegador em http://IP-do-servidor deve mostrar a página predefinida do Apache ou do Nginx (conforme o que arrancou primeiro na porta 80).

Passo 1 — Virtual Hosts no Apache

Virtual hosts permitem servir múltiplos sites a partir do mesmo servidor Apache, distinguindo-os pelo nome de domínio (name-based virtual hosting). Cada site tem a sua própria configuração, directório raiz e logs. A documentação oficial está em Apache Virtual Hosts.

No Debian/Ubuntu, os ficheiros de configuração ficam em /etc/apache2/sites-available/ e activam-se com a2ensite. No RHEL, ficam em /etc/httpd/conf.d/ e ficam activos automaticamente (não há a2ensite). Começar por criar o directório raiz do site.

sudo mkdir -p /var/www/exemplo.pt/public_html
echo "<h1>Site exemplo.pt a funcionar</h1>" | sudo tee /var/www/exemplo.pt/public_html/index.html
sudo chown -R www-data:www-data /var/www/exemplo.pt/

www-data é o utilizador do Apache no Debian. No RHEL, o utilizador é apache — ajustar o chown conforme a distribuição.

Agora criar o ficheiro de virtual host. No Debian/Ubuntu, criar em /etc/apache2/sites-available/exemplo.pt.conf:

<VirtualHost *:80>
    ServerName exemplo.pt
    ServerAlias www.exemplo.pt
    DocumentRoot /var/www/exemplo.pt/public_html

    <Directory /var/www/exemplo.pt/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/exemplo_error.log
    CustomLog ${APACHE_LOG_DIR}/exemplo_access.log combined
</VirtualHost>

Cada directiva tem um propósito específico: ServerName é o domínio principal, ServerAlias permite domínios alternativos, DocumentRoot define o directório base, AllowOverride All permite .htaccess, e Options -Indexes impede a listagem de directórios (segurança).

Activar o site e recarregar a configuração. No Debian, usar a2ensite; no RHEL, basta colocar o ficheiro em conf.d/ e recarregar.

# Ubuntu / Debian
sudo a2ensite exemplo.pt.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

# RHEL / Rocky / AlmaLinux
# (colocar o .conf em /etc/httpd/conf.d/ e usar httpd nas variaveis de log)
sudo httpd -t
sudo systemctl reload httpd

O configtest (ou -t) valida a sintaxe antes de recarregar. Se retornar Syntax OK, é seguro recarregar. Se houver erro, corrigir antes de recarregar — um reload com configuração inválida pode deixar o Apache parado.

Passo 2 — Server Blocks no Nginx

No Nginx, o equivalente aos virtual hosts do Apache chama-se server blocks. A configuração de cada site é um bloco server dentro de um ficheiro .conf. No Debian/Ubuntu, os ficheiros vão em /etc/nginx/sites-available/ (com symlink para sites-enabled/); no RHEL e outras distribuições, vão em /etc/nginx/conf.d/. A documentação oficial está em nginx server_names e ArchWiki Nginx.

Criar o directório raiz (igual ao Apache, mas o utilizador do Nginx é nginx ou www-data conforme a distribuição):

sudo mkdir -p /var/www/exemplo.pt/html
echo "<h1>Nginx — exemplo.pt</h1>" | sudo tee /var/www/exemplo.pt/html/index.html
sudo chown -R www-data:www-data /var/www/exemplo.pt/

Criar o ficheiro de configuração do server block. No Debian/Ubuntu, colocar em /etc/nginx/sites-available/exemplo.pt e criar symlink para sites-enabled. No RHEL, colocar em /etc/nginx/conf.d/exemplo.pt.conf.

server {
    listen 80;
    server_name exemplo.pt www.exemplo.pt;
    root /var/www/exemplo.pt/html;
    index index.html index.htm;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    error_page 404 /404.html;
    access_log /var/log/nginx/exemplo_access.log;
    error_log /var/log/nginx/exemplo_error.log;
}

As directivas principais: listen 80 define a porta, server_name responde pelos domínios, root é o directório base, try_files tenta servir o ficheiro pedido, depois um directório, e se não existir devolve 404. O bloco location ~* activa cache de 30 dias para ficheiros estáticos.

Activar e validar a configuração. O nginx -t testa a sintaxe — deve retornar test is successful.

# Ubuntu / Debian — criar symlink e activar
sudo ln -s /etc/nginx/sites-available/exemplo.pt /etc/nginx/sites-enabled/

# Validar e recarregar (todas as distribuicoes)
sudo nginx -t
sudo systemctl reload nginx

Nota: No Debian/Ubuntu, o ficheiro predefinido /etc/nginx/sites-enabled/default pode capturar todos os pedidos se for o primeiro a ser carregado. Se o site não responder, remover o default com sudo rm /etc/nginx/sites-enabled/default e recarregar.

Passo 3 — Reverse Proxy com Nginx

Um reverse proxy recebe pedidos HTTP no front-end e encaminha-os para um servidor back-end. O caso mais típico é Nginx na porta 80 a fazer proxy para Apache (ou uma aplicação Node.js, Python, etc.) numa porta interna. O Nginx é excelente nisto porque foi desenhado para gerir ligações upstream de forma eficiente. A documentação oficial do módulo de proxy está em ngx_http_proxy_module.

Primeiro, configurar o Apache para escutar na porta 8080 em vez da 80. Editar /etc/apache2/ports.conf (Debian) ou /etc/httpd/conf/httpd.conf (RHEL) e alterar a porta:

# Em ports.conf (Debian) ou httpd.conf (RHEL)
Listen 8080

Depois actualizar o virtual host do Apache para escutar na 8080. Alterar a primeira linha do ficheiro criado no Passo 1:

<VirtualHost *:8080>
    # ... resto igual ao Passo 1
</VirtualHost>

Agora configurar o Nginx como reverse proxy. O Nginx vai receber na porta 80 e encaminhar para Apache na 8080. Os cabeçalhos proxy_set_header são essenciais: sem eles, o Apache back-end não sabe o IP real do cliente nem o host pedido.

server {
    listen 80;
    server_name exemplo.pt www.exemplo.pt;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # Servir ficheiros estaticos directamente (sem proxy)
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
        root /var/www/exemplo.pt/public_html;
        expires 30d;
    }
}

Os cabeçalhos têm funções específicas: Host preserva o domínio pedido, X-Real-IP passa o IP real do visitante (sem isto, o Apache só vê 127.0.0.1), X-Forwarded-For mantém a cadeia de proxies, e X-Forwarded-Proto indica se a ligação original era HTTP ou HTTPS.

Para suporte a WebSockets (aplicações em tempo real, chat, etc.), adicionar estes cabeçalhos dentro do bloco location / — sem eles, o upgrade de protocolo falha e as ligações WebSocket são terminadas. Mais detalhes em Nginx WebSocket.

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

Validar e aplicar:

sudo nginx -t
sudo systemctl reload nginx
sudo systemctl reload apache2  # ou httpd no RHEL

Passo 4 — HTTPS com Let’s Encrypt

Let’s Encrypt emite certificados TLS gratuitos e válidos, com renovação automática. O Certbot é o cliente recomendado — configura o certificado e actualiza o virtual host automaticamente. O domínio tem de apontar para o servidor (registo A no DNS) antes de executar o Certbot, senão a validação falha.

Instalar o Certbot. No Debian/Ubuntu, o pacote inclui plugins específicos para Apache e Nginx que automatizam a configuração do HTTPS.

# Ubuntu / Debian
sudo apt install certbot python3-certbot-apache python3-certbot-nginx

# RHEL / Rocky / AlmaLinux
sudo dnf install certbot python3-certbot-apache python3-certbot-nginx

Para Apache, executar o Certbot com a flag --apache. Para Nginx, usar --nginx. O Certbot detecta os virtual hosts configurados, pede o endereço de email para notificações de expiração, e modifica automaticamente a configuração para usar HTTPS. Mais detalhes em Nginx HTTPS.

# Apache
sudo certbot --apache -d exemplo.pt -d www.exemplo.pt

# Nginx
sudo certbot --nginx -d exemplo.pt -d www.exemplo.pt

O Certbot instala um timer do systemd que renova automaticamente os certificados antes de expirarem (validade de 90 dias). Verificar se o timer está activo:

sudo systemctl list-timers certbot.timer

Para testar a renovação sem esperar, executar sudo certbot renew --dry-run. Se não houver erros, a renovação automática está configurada correctamente.

Passo 5 — Load Balancing com Nginx

O Nginx pode distribuir tráfego entre múltiplos backends usando o bloco upstream. Isto aumenta a disponibilidade e permite escalar horizontalmente — adicionar mais servidores back-end conforme o tráfego cresce. A documentação oficial está em Nginx Load Balancing.

O bloco upstream define o conjunto de servidores back-end. Por defeito, o Nginx usa round-robin (distribuição cíclica). Outros métodos disponíveis: least_conn (menos ligações activas) e ip_hash (mesmo IP vai sempre ao mesmo backend — útil para sessões).

upstream backend_aplicacao {
    # Round-robin (predefinido)
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

# Alternativa: least_conn
# upstream backend_aplicacao {
#     least_conn;
#     server 192.168.1.10:8080;
#     server 192.168.1.11:8080;
# }

# Alternativa: ip_hash (sessoes sticky)
# upstream backend_aplicacao {
#     ip_hash;
#     server 192.168.1.10:8080;
#     server 192.168.1.11:8080;
# }

server {
    listen 80;
    server_name app.exemplo.pt;

    location / {
        proxy_pass http://backend_aplicacao;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Para controlar o comportamento quando um backend falha, adicionar parâmetros a cada servidor: max_fails=3 marca o servidor como indisponível após 3 falhas, fail_timeout=30s define o tempo de espera antes de tentar novamente, e weight=N dá mais peso a servidores mais potentes (recebem mais pedidos).

upstream backend_aplicacao {
    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;  # so usa se os outros falharem
}

Validar e aplicar:

sudo nginx -t
sudo systemctl reload nginx

Erros Comuns

Os erros mais frequentes ao configurar Apache e Nginx, com causa e solução directa:

Problema Causa Solução
“(98) Address already in use” no arranque Outro processo ja escuta na porta 80 ou 443 Executar ss -tlnp | grep ':80' para identificar o processo e para-lo, ou mudar a porta
403 Forbidden Permissoes incorrectas no directorio raiz ou falta de index Verificar chown e chmod 755 no directorio. Confirmar que existe index.html
502 Bad Gateway Backend (Apache/app) nao esta a correr ou a porta do proxy_pass esta errada Verificar systemctl status apache2. Confirmar que a porta no proxy_pass corresponde a do backend
SSL_ERROR_RX_RECORD_TOO_LONG Aceder a HTTPS numa porta que serve HTTP (ou vice-versa) Confirmar que listen 443 ssl esta configurado e que o certificado esta instalado
.htaccess ignorado Nginx nao suporta .htaccess; no Apache, AllowOverride pode estar None No Apache: mudar para AllowOverride All. No Nginx: converter regras para a config do Nginx
“conflicting server name” no log Dois server blocks com o mesmo server_name Remover duplicados. O Nginx usa o primeiro que encontra, ignorando o resto
Site carrega sem CSS/imagens Caminho root errado ou ficheiros estaticos a passar pelo proxy Verificar directiva root. Adicionar bloco location para estaticos no Nginx

Checklist de Verificação

Antes de colocar o servidor em produção, verificar cada ponto desta lista. Estes passos evitam os erros mais comuns e garantem que a configuração está consistente.

  1. Testar a sintaxe: executar sudo nginx -t e sudo apache2ctl configtest (ou httpd -t) antes de cada reload.
  2. Verificar portas: confirmar que nao ha conflito com ss -tlnp | grep -E ':80|:443|:8080'. Apenas um servico por porta.
  3. Testar permissões: ls -la /var/www/exemplo.pt/ — o utilizador do web server deve ser dono dos ficheiros.
  4. Confirmar DNS: o dominio tem de apontar para o IP do servidor. Testar com dig exemplo.pt ou nslookup exemplo.pt.
  5. Verificar HTTPS: aceder a https://exemplo.pt no navegador e confirmar que o cadeado aparece sem avisos. Usar SSL Labs para um teste completo.
  6. Confirmar renovação automatica: executar sudo certbot renew --dry-run e verificar systemctl list-timers certbot.timer.
  7. Verificar logs: tail -f /var/log/nginx/error.log e tail -f /var/log/apache2/exemplo_error.log — nao devem ter erros 502, 503 ou 403.

Artigos Relacionados