# 01 — WHM / cPanel: configuração de servidor (camada L0)

> **Escopo:** servidor (root/WHM), não conta cPanel, não `.htaccess`.
> **Stack:** AlmaLinux + cPanel/WHM + Apache 2.4 + EasyApache 4 + `ea-php72` (PHP 7.2.34).
> **Fonte principal:** `v6-hardening/wiki/infra-update/WHM.md`,
> `correcao-htaccess-whm-performance-l0.md` e `WHM_EXTENSIONS.md`.
> **Cópias literais:** `arquivos/whm.php.ini.recomended`, `arquivos/php.ini.recomended`.

---

## 1. Por que essas mudanças são manuais

O EPIC #23410 tentou, inicialmente, resolver compressão e OPcache pela conta
(`.htaccess` e `.user.ini`). A investigação empírica no cPanel/WHM mostrou que:

1. **Compressão via `.htaccess` é sobreposta.** O `AddOutputFilterByType DEFLATE`
   da conta é ignorado/sobreposto pelo vhost do EasyApache 4. A única via validada
   foi a flag server-side **cPanel “Optimize Website”** (ganho medido de **−62,1%
   no transfer** no estágio 1) ou um include global no WHM. Brotli exige
   `mod_brotli` provisionado no EA4.
2. **`opcache.*` e `realpath_cache_*` são `PHP_INI_SYSTEM`.** Valores no
   `php.ini`/`.user.ini` da conta são **ignorados silenciosamente**. Só o php.ini
   de sistema do EA4 (`/opt/cpanel/ea-php72/root/etc/php.ini`) vale.
3. **Não há script rodando no WHM.** Essas frentes são aplicadas manualmente,
   com o passo a passo abaixo.

### O que funciona na conta (via `.htaccess`)

- `Header` cruas (sem `<IfModule mod_headers.c>`).
- `mod_expires` (com wrapper seguro).
- `FilesMatch`, `FileETag`, `Rewrite*`.

### O que NÃO funciona na conta

- `mod_deflate`/`mod_brotli` (sobrepostos pelo vhost).
- `opcache.*` (`PHP_INI_SYSTEM`).
- `realpath_cache_*` (`PHP_INI_SYSTEM`).
- `apcu.enabled`/`apcu.enable_cli` (`PHP_INI_SYSTEM`).

---

## 2. Ordem de aplicação

```
P0-A (compressão) → P0-B (OPcache) → P0-C (APCu) → verificação
```

Aplicar primeiro no **canário** (`infra-update.devs.maxima.inf.br`), medir e só
depois liberar para os demais domínios.

---

## 3. P0-A — Compressão (gzip global + brotli)

### 3.1 gzip via cPanel “Optimize Website” (caminho validado)

1. Acesse o **cPanel** da conta do portal.
2. Vá em **Software » Optimize Website** (pode aparecer como “Otimizar Site”).
3. Selecione **“Compress all content”**.
4. Clique em **Save**.

Isso habilita `mod_deflate` no vhost da conta para todos os tipos de texto.
O EasyApache 4 já traz `mod_deflate`; a flag é o caminho homologado.

### 3.2 gzip global via WHM (opcional, todas as contas)

1. WHM » **Service Configuration » Apache Configuration » Include Editor**.
2. No include de `pre_main_global` ou `pre_virtualhost_global`, garantir:

```apache
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
    AddOutputFilterByType DEFLATE application/javascript application/json application/xml
    AddOutputFilterByType DEFLATE application/x-javascript image/svg+xml font/ttf font/otf
</IfModule>
```

3. **Save** — o Apache reinicia sozinho.

> **Não** colocar esse bloco no `public/.htaccess`: o vhost do EA4 sobrepõe.

### 3.3 Brotli (bônus, não bloqueia)

Brotli é superior a gzip para texto. É módulo opcional do EasyApache 4:

1. WHM » **Software » EasyApache 4**.
2. **Customize** em “Currently Installed Packages” ou “Customize Profiles”.
3. Aba **Apache Modules**.
4. Buscar **`brotli`** → marcar **`mod_brotli`**.
5. Aba **Review** » **Provision** (rebuild). O Apache reinicia automaticamente.
6. Opcional: adicionar ao include global:

```apache
<IfModule mod_brotli.c>
    AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript
    AddOutputFilterByType BROTLI_COMPRESS application/javascript application/json application/xml
    AddOutputFilterByType BROTLI_COMPRESS application/x-javascript image/svg+xml font/ttf font/otf
</IfModule>
```

7. **Save/Provision** novamente.

### 3.4 Verificação de compressão

```bash
# deve retornar Content-Encoding: gzip (ou br)
curl -sI --compressed https://infra-update.devs.maxima.inf.br/ \
  | grep -i content-encoding

# comparar transfer contra baseline
python3 ../comparar/comparar.py \
  measuring/results/2026-09-08-12-10 \
  measuring/results/<nova-data>
# esperado: compression = 1.0 e transfer com queda (~ -62% no estágio 1)
```

### 3.5 Rollback de compressão

- cPanel » Optimize Website » **Disabled**.
- Ou remover o include do WHM e salvar.
- Brotli: WHM » EasyApache 4 » Customize » desmarcar `mod_brotli` » Provision.

---

## 4. P0-B — OPcache no `ea-php72`

### 4.1 Status no snapshot

A documentação registra que, em 2026-09-09, o phpinfo real confirmou:

- `opcache.enable = On`
- `opcache.validate_timestamps = Off`

O ajuste pendente era subir o teto de arquivos/memória:

| Diretiva | Antes (documentado) | Produção recomendada |
|---|---:|---:|
| `opcache.memory_consumption` | 128 | **256** |
| `opcache.interned_strings_buffer` | 8 | **32** |
| `opcache.max_accelerated_files` | 4000 (ou 16229) | **10000** |
| `opcache.revalidate_freq` | 15 | **60** |
| `opcache.validate_timestamps` | 1 | **0** |
| `opcache.save_comments` | — | **1** |
| `realpath_cache_size` | — | **4096K** |
| `realpath_cache_ttl` | — | **600** |

> O `php.ini.recomended` da conta ainda lista `memory_consumption=128`,
> `max_accelerated_files=16229`, `validate_timestamps=1` no bloco “visual”.
> Isso **não** é o que vale: o valor real é o do WHM/system.

### 4.2 CUIDADO: é o php.ini DE SISTEMA

O php.ini aberto em “System” é global
(`/opt/cpanel/ea-php72/root/etc/php.ini`) e o cPanel **o regera em updates do EA4**.
Portanto:

- **Só chaves `PHP_INI_SYSTEM` valem aqui** (OPcache, realpath, `apcu.*`).
- **Não cole o `php.ini.recomended` inteiro** no sistema: ele contém endurecimento
  de conta (`expose_php`, `session.*`, limites, `pcre.jit`, `zend.assertions`).
- **Não use `error_log = "/home/USUARIO/..."` no global**: isso direciona o log de
  todas as contas para um único diretório (ou falha).
- Aplique via **WHM » MultiPHP INI Editor » System » ea-php72** (UI), não editando
  o arquivo na mão — assim a mudança sobrevive a rebuilds do EA4.
- O arquivo pronto é `arquivos/whm.php.ini.recomended`.

### 4.3 Passo a passo no WHM

1. WHM » **Software » MultiPHP INI Editor**.
2. No seletor superior, escolha **System** (não “Basic/Editor” de conta).
3. No seletor de versão, escolha **`ea-php72`**.
4. O editor abre `/opt/cpanel/ea-php72/root/etc/php.ini`.
5. Localize/adicione as chaves e **substitua** valores existentes (não duplique):

```ini
opcache.enable = 1
opcache.memory_consumption = 256
opcache.interned_strings_buffer = 32
opcache.max_accelerated_files = 10000
opcache.revalidate_freq = 60
opcache.validate_timestamps = 0
opcache.save_comments = 1
realpath_cache_size = 4096K
realpath_cache_ttl = 600
```

6. **Save**. O WHM grava no php.ini de sistema e recarrega o PHP-FPM/Apache.
7. Se houver múltiplos handlers PHP-FPM por conta, o WHM reinicia o `php-fpm` do
   `ea-php72`.

### 4.4 Consequência de `validate_timestamps=0`

O OPcache **não** checa mtime a cada request (ganho de performance). Isso exige
**reset do OPcache a cada deploy**. Sem reset, código novo não carrega.

Formas de reset:

```bash
# 1) via aplicação (precisa existir o comando/pacote)
php artisan opcache:clear

# 2) via web (o reset precisa rodar no SAPI FPM, não no CLI)
#    ex.: endpoint temporário /opcache-reset.php com opcache_reset();
#         remover depois.

# 3) tocar o entrypoint (não é garantido em todos os setups)
touch /caminho/public/index.php

# 4) reiniciar/reload do PHP-FPM (root) — forma mais confiável
whmapi1 restart_service service=php-fpm
# ou:
/scripts/restartsrv_apache_php_fpm
# ou:
service php-fpm restart
```

> ⚠️ **Nuance importante:** `php -r 'opcache_reset();'` executado no **CLI** limpa o
> OPcache do processo CLI, **não** o do PHP-FPM. Para o FPM, use restart/reload ou
> um endpoint HTTP que execute `opcache_reset()` dentro do FPM.

### 4.5 Verificação do OPcache

```bash
# no servidor, como root, no contexto do ea-php72
/opt/cpanel/ea-php72/root/usr/bin/php -i \
  | grep -E 'opcache.enable|opcache.memory_consumption|opcache.validate_timestamps'

# esperado:
#   opcache.enable => On
#   opcache.memory_consumption => 256
#   opcache.validate_timestamps => Off
```

> Pela rede, o phpinfo da conta não reflete necessariamente o OPcache de sistema.
> Confie no check acima.

### 4.6 Rollback do OPcache

WHM » MultiPHP INI Editor » System » `ea-php72` → restaurar os valores originais
(ex.: `memory_consumption=128`, `max_accelerated_files=16229`,
`validate_timestamps=1`) e salvar. Reiniciar o FPM.

---

## 5. P0-C — APCu autoloader

### 5.1 O que é e por que importa

O `apcu-autoloader` (ativo em `composer.json` via `"apcu-autoloader": true`) guarda
o **class map** do Composer na memória compartilhada do APCu, acelerando o autoload.
A chave do `composer.json` só surte efeito se a extensão APCu estiver instalada e
habilitada no servidor.

### 5.2 Status real neste ambiente — evidência conflitante

Os próprios documentos do snapshot divergem:

| Fonte | Afirmação |
|---|---|
| `WHM.md` §C.1 (2026-09-09) | **ATIVO** via `pecl install apcu` + `zzz-apcu.ini` |
| `WHM.md` tabela final | **N/A** neste ambiente |
| `correcao-htaccess-whm-performance-l0.md` | **NÃO aplicado** (RPM EA4 ausente; pulado) |
| `correcao-cache-control-borda-l1.md` §7 | **Ativo** via build manual |

**Conclusão conservadora:** trate o APCu como **opcional e não garantido**. Antes
de contar com o `apcu-autoloader`, valide no host real:

```bash
/opt/cpanel/ea-php72/root/usr/bin/php -m | grep -i '^apcu$'
/opt/cpanel/ea-php72/root/usr/bin/php -r "var_dump(extension_loaded('apcu'));"
```

- O pacote RPM `ea-php72-php-apcu` **não existe** no repositório EA4 deste servidor
  CloudLinux. `yum install ea-php72-php-apcu` falha com “Nenhuma correspondência”.
- No CloudLinux, o APCu só vem via alt-php/PHP Selector, que **não se aplica** porque
  o domínio roda **ea-php72 (EA4)**.
- Se a extensão estiver presente, o método documentado é **build manual via
  `pecl install apcu`**, gerando `apcu.so` em
  `/opt/cpanel/ea-php72/root/usr/lib64/php/modules/` e ini em
  `/opt/cpanel/ea-php72/root/etc/php.d/zzz-apcu.ini`.
- Se **não** estiver, o `composer dump-autoload --apcu` apenas emite warning e o
  deploy continua; não trate a ausência como falha.

### 5.3 Instalação manual (root) — somente se a extensão for necessária

Se a validação de §5.2 confirmar que o APCu **não** está presente e você decidir
usá-lo, o método documentado é:

1. Build com pecl (responder `no` ao prompt de debug interno):

```bash
/opt/cpanel/ea-php72/root/usr/bin/pecl install apcu
```

- Compila `apcu.so` e instala em
  `/opt/cpanel/ea-php72/root/usr/lib64/php/modules/apcu.so`.
- O pecl pode adicionar `extension=apcu.so` ao php.ini ou pedir confirmação; se não
  adicionar, criar o ini abaixo.

2. Criar o ini no scan dir do EA4:

```bash
cat > /opt/cpanel/ea-php72/root/etc/php.d/zzz-apcu.ini <<'EOF'
extension=apcu.so
apcu.enabled=1
apcu.enable_cli=1
apc.shm_size=32M
EOF
```

3. Reiniciar o FPM:

```bash
whmapi1 restart_service service=php-fpm
# ou:
/scripts/restartsrv_apache_php_fpm
```

4. Validar:

```bash
/opt/cpanel/ea-php72/root/usr/bin/php -m | grep -i apcu   # apcu
```

> ⚠️ **RISCO:** build manual é **sobrepujado por rebuild/update do EasyApache 4** —
> o `apcu.so` some e o PHP-FPM não sobe (`extension=apcu.so` órfão). Se ocorrer,
> remova/comente a linha do ini ou reinstale via `pecl`. Revalide após todo update
> do EA4.

### 5.4 Beneficiar o CLI (opcional)

Por padrão o APCu em CLI vem desligado (`apcu.enable_cli = 0`), então o web
(php-fpm) aproveita o cache, mas `php artisan`/cron não. No php.ini de sistema:

```ini
apcu.enabled = 1
apcu.enable_cli = 1
apc.shm_size = 32M
```

`apcu.enabled` e `apcu.enable_cli` são `PHP_INI_SYSTEM` → só no WHM, nunca no
`.user.ini` da conta. `apc.shm_size` controla a memória compartilhada (32M costuma
bastar para o class map; aumente se houver muitas classes).

### 5.5 Regenerar o autoloader

```bash
composer dump-autoload
# Composer deve indicar "with APCu" ao gerar os arquivos otimizados
```

### 5.6 Degradação graciosa

Se o APCu não estiver instalado, o `composer dump-autoload` emite um *warning*
(“The apcu-autoloader option is set but the extension is not enabled”) e segue
**sem cache** — o deploy não quebra. Por isso `ext-apcu` **não** foi adicionado ao
`require` do `composer.json`.

### 5.7 Verificação do APCu

```bash
# extensão presente no ea-php72
/opt/cpanel/ea-php72/root/usr/bin/php -m | grep -i apcu

# autoloader gerado usa APCu
grep -l "apcu_fetch" vendor/composer/autoload_real.php \
  && echo "APCu autoloader ATIVO"

# Composer confirma ao regerar
composer dump-autoload -v    # esperado: "... with APCu"
```

### 5.8 Rollback do APCu

Remover `/opt/cpanel/ea-php72/root/etc/php.d/zzz-apcu.ini` (ou comentar
`extension=apcu.so`) e reiniciar o FPM. O `"apcu-autoloader": true` no
`composer.json` pode ficar (degrada graciosamente).

---

## 6. CloudLinux PHP Selector (`WHM_EXTENSIONS.md`) — quando se aplica

O documento `WHM_EXTENSIONS.md` descreve a ativação global de extensões via
**CloudLinux PHP Selector / alt-php**. Ele **não** é o cenário do domínio deste
snapshot (EA4 `ea-php72`). Mantido como referência para ambientes alt-php.

### 6.1 Conceito (evita erro comum)

- Os **“módulos padrão”** do Selector valem **só para contas NOVAS**.
- Contas já criadas mantêm a escolha que tinham; definir o default **não** reaplica
  nelas.
- Para cobrir todas as estruturas: (1) default no WHM + (2) propagar para contas
  existentes com `selectorctl` + (3) reconstruir CageFS.
- **Habilitar a extensão ≠ tunar o cache.** As diretivas `opcache.*` ficam em
  `/opt/alt/php72/etc/php.ini` (alt-php) e no php.ini do ea-php (sistema).

### 6.2 Default para novas contas (WHM UI)

1. WHM → **Server Configuration → CloudLinux Manager → aba Selector**.
2. Em **“Choose default modules for”**, selecionar **alt-php72** (e cada versão em uso).
3. Marcar **opcache** (e **apcu**, opcional) e **Save**.
4. Opcional: *Hide PHP extensions for end-user* para impedir que usuários desliguem
   o que foi definido.

### 6.3 Forçar em todas as contas existentes

```bash
# opcache + apcu em alt-php72 para TODOS os usuarios
selectorctl --enable-user-extensions=opcache,apcu --version=7.2 --for-all-users

# idem para TODAS as versoes alt-php em uso
for i in $(selectorctl -S | grep 'e' | awk '{print $1}' | grep -v native); do
  selectorctl --enable-user-extensions=opcache,apcu --version=$i --for-all-users
done
```

> Para **resetar** as escolhas de todos aos defaults:
> `selectorctl --cl-selector-reset-modules` — cuidado, sobrepõe a escolha manual do
> usuário.

Outros comandos úteis:

```bash
selectorctl -S                                            # lista versoes alt-php
selectorctl --list-extensions --version=7.2              # + habilitado / - desabilitado / ~ compilado
selectorctl --set-current=7.2                            # versao padrao global (novas contas)
selectorctl --enable-extensions=opcache,apcu --version=7.2   # default (defaults.cfg), nao por conta
selectorctl --user-summary --user=USUARIO --show-native-version
```

### 6.4 CageFS

```bash
/usr/sbin/cagefsctl --force-update-all
```

### 6.5 Diretivas do OPcache em alt-php

Editar `/opt/alt/php72/etc/php.ini`:

```ini
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
opcache.revalidate_freq=0

; (opcional) APCu
apcu.enabled=1
apcu.enable_cli=1
apcu.shm_size=32M
```

> Domínios em PHP “native”/ea-php72 (sistema): tunar no **WHM → MultiPHP INI Editor**
> (System / ea-php72).

### 6.6 Validação em alt-php

> **CLI desliga opcache/apcu por padrão** (`opcache.enable_cli=Off`,
> `apcu.enable_cli=Off`). `opcache_get_status()`/`apcu_cache_info()` no CLI dão
> falso-negativo. A verificação definitiva é em contexto **web** (HTTP/phpinfo) ou
> forçando as flags de CLI só no teste.

```bash
selectorctl --list-extensions --version=7.2 | grep -i opcache   # esperado: + opcache
su - USUARIO -s /bin/bash -c 'php -m | grep -iE "opcache|apcu"'
su - USUARIO -s /bin/bash -c 'php -i | grep -iE "opcache.enable|apcu.enabled"'
php -d opcache.enable_cli=1 -r 'var_dump(opcache_get_status()["opcache_enabled"]);'
php -d apcu.enable_cli=1 -r 'var_dump(apcu_cache_info());'
```

### 6.7 Redis neste ambiente

Há duas informações conflitantes nos documentos:

- `WHM_EXTENSIONS.md` §7 diz que `redis` não funciona no PHP 7.2.34 deste servidor e
  que `CACHE_DRIVER=redis`/`SESSION_DRIVER=redis` ficam **deferidos/ignorados**.
- `correcao-cache-control-borda-l1.md` §7 (posterior, EA4) diz que `redis` está
  disponível na conta e que `CACHE_DRIVER=redis`/`SESSION_DRIVER=redis` é viável
  para L2.2/L2.3.

**Decisão conservadora:** o snapshot opera com `CACHE_DRIVER=file` e
`SESSION_DRIVER=file`. Só migrar para Redis após validar a extensão e o serviço no
servidor real, com teste de sessão e invalidação do FPC.

---

## 7. Tabela de responsabilidade

| Item | Onde se faz | Quem | Risco |
|---|---|---|---|
| gzip (Optimize Website) | cPanel/WHM | root/conta | baixo |
| `mod_brotli` | WHM EasyApache 4 | root | baixo |
| OPcache `ea-php72` | WHM MultiPHP INI Editor (System) | root | baixo |
| APCu autoloader | **build manual pecl** (EA4), se a extensão existir; status conflitante | root | médio (rebuild EA4) |
| reset OPcache no deploy | app / deploy.sh / FPM restart | deploy/root | baixo |
| Cache-Control/ETag estáticos | `public/.htaccess` (conta) | repo | baixo |
| php.ini de conta | cPanel MultiPHP INI Editor/.user.ini | conta | baixo |
| PHP Selector/alt-php | **não se aplica** neste domínio | — | — |

---

## 8. Checklist final de servidor

- [ ] `curl -sI --compressed` retorna `Content-Encoding: gzip` (ou `br`).
- [ ] `opcache.enable=On`, `memory_consumption=256`, `validate_timestamps=Off`.
- [ ] `realpath_cache_size=4096K`, `realpath_cache_ttl=600`.
- [ ] `php -m | grep apcu` retorna `apcu` **se** o APCu for usado; se não, `composer dump-autoload --apcu` apenas emite warning. Validar antes de contar com o ganho.
- [ ] `php.ini` da conta aplicado com `error_log` do usuário correto.
- [ ] `X-Hardening-Version` presente nas respostas.
- [ ] HTTP/2 + TLS 1.3 mantidos, redirects=0, disponibilidade=100%.
- [ ] Após cada deploy: reset/reload do PHP-FPM.
- [ ] Após cada update do EA4: revalidar APCu (se instalado) e OPcache.
- [ ] `composer.lock` presente/versionado (reprodutibilidade) — ver F-04.
- [ ] Confirmar que o APCu não é necessário para o boot (degradação graciosa).
