Hardening e Troubleshooting em HestiaCP: Guia Definitivo de Sobrevivência
Este artigo consolida uma sequência real de troubleshooting em ambiente HestiaCP + Nginx + PHP + Flysystem. O foco aqui é causa-raiz, hardening de verdade e correção na camada certa. Nada de “apagar incêndio” sem entender o porquê.
Durante a operação, o padrão foi sempre o mesmo: validar evidência no log, mapear a camada afetada (aplicação, PHP-FPM, Nginx, SSL) e aplicar a correção definitiva no ponto de controle correto.
Stack do cenário
- Painel: HestiaCP (sucessor espiritual do Vesta, com perfil mais enxuto e seguro).
- Webserver: Nginx (reverse proxy) + PHP-FPM.
- FileSystem: PHP League Flysystem.
- SSL: Let’s Encrypt.
---
1) O dilema do open_basedir com Flysystem
Sintoma observado
Mesmo com chmod aparentemente correto, a aplicação falhava ao criar diretórios:
League\Flysystem\Exception: Impossible to create the root directory
mkdir(): open_basedir restriction in effect.
Diagnóstico de campo
open_basedir não é problema, é proteção. Ele enclausura o PHP no escopo permitido. O erro aparece quando o path do app não bate com o contexto do pool PHP-FPM.
Exemplo clássico:
- Script rodando como
userA(pool do domínio). - App tentando escrever em
/home/userB/.... - Resultado: bloqueio imediato por política.
Correção estratégica
Opção 1 — Alinhamento de path (recomendado)
Ajustei o Flysystem para usar path dentro do escopo do domínio (ou relativo ao DOCUMENT_ROOT), evitando acesso fora do “jardim cercado” do usuário.
Opção 2 — Ajuste no template PHP-FPM do domínio
Quando acesso externo é realmente necessário, a correção é no template do backend daquele domínio, não no php.ini global:
php_admin_value[open_basedir] = "/home/userA/web/domain.com/public_html:/tmp:/usr/share/php:/home/outro-diretorio-necessario"
Ponto de hardening: nunca desabilitei open_basedir. Em ambiente multi-tenant, isso é camada crítica contra Directory Traversal e leitura indevida entre contas.
---
2) “Renomear usuário” no HestiaCP: verdade operacional
Fato técnico
Não existe rename nativo de usuário no HestiaCP. O username está acoplado a:
- paths em
/home/... - ownership (
uid/gid) - nomes de banco prefixados
- logs e contexto operacional
Workflow correto de migração
Para trocar antigo para novo, executei ciclo completo:
- Criação do usuário
novono painel. - Recriação dos domínios/zona DNS sob
novo. - Migração de dados preservando estrutura/permissão:
rsync -av /home/antigo/web/domain.com/public_html/ /home/novo/web/domain.com/public_html/
chown -R novo:novo /home/novo/web/domain.com/public_html
- Dump/restore de banco, lembrando o prefixo por usuário (
antigo_db->novo_db).
Sem isso, o ambiente fica parcialmente funcional e o erro aparece depois em permissões, cron ou conexão de app.
---
3) Ajuste fino de timeouts no Nginx para eliminar 504
Quando a app faz upload pesado, processamento longo ou relatório complexo, timeout padrão (60s) vira gargalo e o cliente recebe:
504 Gateway Timeout
Onde ajustar no HestiaCP
Não alterei /etc/nginx/nginx.conf diretamente, porque o painel pode sobrescrever. O local correto foi:
Domain -> Advanced Options -> Custom Nginx Configuration
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
send_timeout 300;
fastcgi_read_timeout 300;
Depois da gravação, validei se o domínio recebeu a configuração gerada e confirmei com requisição de teste no endpoint mais pesado.
---
4) Debugging de front-end e SSL sem “achismo”
4.1 addEventListener em elemento nulo
Erro recorrente:
Cannot read properties of null (reading 'addEventListener')
Não era bug do navegador; era script executando antes de o elemento existir naquela rota.
Correção aplicada com validação defensiva:
const btnLogin = document.getElementById('btnLogin');
btnLogin?.addEventListener('click', (e) => {
console.log('Login disparado');
});
Esse padrão reduz quebra silenciosa quando o mesmo bundle JS roda em páginas com DOM diferente.
4.2 Service Worker e SSL: requisito rígido
Service Worker exige HTTPS válido. Se certificado expira ou cadeia está incompleta, o navegador bloqueia registro por segurança.
Checklist que executei:
- Conferir se SSL e Let’s Encrypt estão ativos no domínio.
- Confirmar redirect forçado para HTTPS.
- Validar handshake/cadeia com:
curl -Iv https://domain.com
Se cadeia vier incompleta, o front-end pode parecer “aleatoriamente quebrado” em cache/PWA, quando a causa real está no TLS.
---
5) Consolidação técnica por causa-raiz
| Problema | Causa raiz | Solução de engenharia |
|---|---|---|
open_basedir bloqueando Flysystem | Path fora do escopo do pool | Alinhar path ou estender escopo no template PHP-FPM do domínio |
| “Renomear usuário” | Limitação arquitetural | Migração assistida: novo user + rsync + chown + dump/restore |
504 Gateway Timeout | Timeout padrão insuficiente | Injetar proxy_* e fastcgi_read_timeout em Advanced Options |
addEventListener em null | Script rodando sem elemento no DOM | Optional chaining e validação defensiva |
| Falha de SW/PWA | SSL inválido/cadeia incompleta | Reemitir LE, validar cadeia e forçar HTTPS consistente |
---
6) Lições práticas de quem opera infraestrutura
- Configuração ganha de código: grande parte dos erros “sem explicação” nasce em política de runtime (FPM, Nginx, permissões), não no framework.
- Proteção não é bug:
open_basedire exigência HTTPS para SW existem para impedir incidente real. - Respeite a camada de controle: no HestiaCP, use templates e Advanced Options. Alteração manual fora do fluxo do painel tende a regressão.
Conclusão
Troubleshooting eficiente não é “testar até passar”. É ler log, mapear hierarquia da stack e corrigir no ponto exato. Quando você aplica esse método, o ambiente para de oscilar e passa a ser previsível.
Em produção, previsibilidade é segurança.
Este post está licenciado sob CC BY-NC.
Comentários
Participe da discussão abaixo.
Comentários ainda não configurados. Adicione as opções do Cusdis em /assets/json/config/blog-comments-config.json.