A trincheira: quando painel, runtime e proxy quebram em cadeia#
A linha entre uma manutenção de rotina e um ambiente em colapso é tênue. Recentemente, encarei uma daquelas longas sessões de troubleshooting em um servidor de produção rodando Rocky Linux com aaPanel. O que começou como uma simples tentativa de restaurar o acesso ao painel de controle evoluiu para uma sequência de armadilhas envolvendo WSGI/Gevent, tipagem estrita no Python 3.12, conflitos de UID no Nginx e um loop de proxy reverso invisível.
Abaixo, documento o passo a passo da investigação, os erros encontrados no terminal e as decisões adotadas.
1. O ponto de partida: painel silencioso e o enigma do exit_group(0)#
O cenário inicial era de indisponibilidade total do painel web. As tentativas de resetar arquivos de controle e redefinir a porta via CLI não surtiam efeito:
touch /www/server/panel/data/setup.pl
touch /www/server/panel/data/install.pl
echo "8888" > /www/server/panel/data/port.pl
rm -f /www/server/panel/logs/panel.pid
fuser -k 8888/tcp
./pyenv/bin/python tools.py port 8888
# Retorno: ERROR: Parameter error
Ao tentar invocar o binário BT-Panel manualmente com o PYTHONPATH ajustado, o processo encerrava sem mensagens no terminal.
Rastreando com strace#
Para entender o comportamento do interpretador, executei o rastreio de chamadas de sistema:
cd /www/server/panel
export PYTHONPATH=$PYTHONPATH:/www/server/panel/class
strace -e trace=openat,write,exit_group ./pyenv/bin/python BT-Panel
A saída mostrou a carga de dependências (gevent, greenlet, flask), leitura do PID em /www/server/panel/logs/panel.pid e, em seguida:
exit_group(0) = ?
+++ exited with 0 +++
O processo não sofria um crash com SIGSEGV. Tratava-se de um encerramento lógico limpo: o BT-Panel implementa controle de instância única (Singleton). Se detecta travas de arquivo residuais, encerra imediatamente.
2. A camada de proxy interno e a recuperação do admin_path#
Ao reiniciar os serviços via bt 1, o comando ss confirmou a porta 8888 aberta:
ss -tulnp | grep 8888
# tcp LISTEN 0 511 0.0.0.0:8888 users:(("webserver",pid=251923,fd=7)...)
A porta não pertencia diretamente ao binário Python, mas sim ao Nginx interno do painel (webserver). Contudo, acessar a raiz pelo navegador retornava 404.
O teste via curl revelou a causa:
curl -I http://127.0.0.1:8888$(cat /www/server/panel/data/admin_path.pl)
# cat: /www/server/panel/data/admin_path.pl: No such file or directory
# HTTP/1.1 404 NOT FOUND
O arquivo com o caminho secreto (admin_path.pl) havia sido apagado. O aaPanel recusa requisições na raiz por segurança. Alterei a porta para 1606 e recriei o arquivo:
echo "/evolya" > /www/server/panel/data/admin_path.pl
bt 1
O login voltou a responder em http://IP_DO_SERVIDOR:1606/evolya.
3. Erro de desserialização no Python 3.12 (panelplugin.py)#
Ao autenticar no painel, a rota POST /v2/ajax?action=RepPanel gerou uma exceção:
Traceback (most recent call last):
File "/home/www/server/panel/class/panelPlugin.py", line 1585, in getPluginList
arr[i]['end'] = '--'
TypeError: 'str' object does not support item assignment
Causa raiz#
No Python 3.12, o tratamento de tipos é rigoroso. A rotina getPluginList esperava uma lista de dicionários com metadados de plugins. Por conta de cache truncado da API remota, itens da lista foram carregados como strings simples (str), quebrando a atribuição de chaves.
Adicionalmente, ao ajustar o ambiente virtual, o pacote correto a ser instalado para o módulo yaml é PyYAML:
./pyenv/bin/pip3 install PyYAML
4. O mistério do 403 forbidden e atributos imutáveis no Linux#
Com o painel restabelecido, os sites hospedados retornavam 403 Forbidden.
O atributo imutável (+i)#
Ao tentar ajustar as permissões do webroot, o sistema bloqueou:
chown -R www:www /home/user/public_html
# chown: changing ownership of '/home/user/public_html/.user.ini': Operation not permitted
O arquivo .user.ini estava com o bit de imutabilidade ativado (+i). Além disso, diretórios criados por ferramentas de IA continham links simbólicos que falhavam no chattr -R. A solução foi aplicar a remoção pontual no arquivo:
chattr -i /home/user/public_html/.user.ini
chown -R www:www /home/user/public_html
chmod 755 /home/user
chmod 755 /home/user/public_html
5. Cegueira de configuração e conflito de UID no Nginx#
Ao inspecionar a configuração carregada pelo Nginx em memória:
/usr/sbin/nginx -T | grep "server_name"
# server_name _;
O Nginx do sistema estava lendo apenas /etc/nginx/nginx.conf, ignorando os Vhosts em /www/server/panel/vhost/nginx/. Além disso, o binário do sistema rodava sob o usuário nginx:nginx, enquanto os arquivos da aplicação pertenciam a www:www.
Correção de identidade e includes#
- Incluí o diretório de Vhosts no arquivo principal:
sed -i '/include \/etc\/nginx\/conf.d\/\*\.conf;/a \ include /www/server/panel/vhost/nginx/*.conf;' /etc/nginx/nginx.conf
- Ajustei o usuário de execução para
www:
sed -i 's/^user nginx;/user www www;/g' /etc/nginx/nginx.conf
- Testei a configuração:
/usr/sbin/nginx -t
6. A causa raiz: o loop do multi-webserver hosting#
Ao subir o site, o arquivo /www/wwwlogs/domain.com.br.error.log registrou:
connect() failed (111: Connection refused) while connecting to upstream, client: IP, server: domain.com.br, request: "GET /index.html HTTP/1.1", upstream: "https://127.0.0.1:8290/index.html"
O servidor estava com a opção Multi-Webserver Hosting ativada. O Apache backend escutava na porta interna 8290, mas configurado em HTTP claro. O Vhost do Nginx estava configurado com proxy_pass https://127.0.0.1:8290; tentando negociar TLS com um socket HTTP comum, causando recusa imediata de conexão.
7. Protocolo de evacuação e reconstrução limpa#
Após mapear todas as camadas de falha (drift de binários do Python, desserialização de metadados, permissões corrompidas e loop de proxy reverso), manter o host com correções paliativas traria instabilidade futura.
Executei a evacuação completa de dados:
# 1. Backup de todos os bancos de dados
mkdir -p /backup_evacuation/db
for db in $(mysql -e "show databases;" -s --skip-column-names | grep -vE "performance_schema|information_schema|mysql|sys"); do
mysqldump --opt --single-transaction "$db" > "/backup_evacuation/db/${db}.sql"
done
# 2. Empacotamento dos arquivos limpos
mkdir -p /backup_evacuation/www
tar -czvf /backup_evacuation/www/sites.tar.gz \
--exclude='.user.ini' \
--exclude='*.log' \
--exclude='.agent' \
--exclude='.claude' \
--exclude='.cursor' \
/www/wwwroot /home/user/public_html
# 3. Preservacao dos certificados SSL
cp -a /www/server/panel/vhost/cert /backup_evacuation/ssl_certs
Principais aprendizados do post-mortem#
- Prefira pilhas diretas: Manter uma arquitetura limpa com Nginx Standalone + PHP-FPM evita camadas desnecessárias de proxy loopback e falhas de negociação TLS em portas locais.
- Atenção aos UIDs: Binários de pacotes do sistema (
nginx:nginx) costumam divergir dos usuários de painéis web (www:www). Alinhe a diretivauserno arquivo mestre do Nginx. - Saiba a hora de reconstruir: Isolar a causa raiz é fundamental para o diagnóstico, mas reconhecer quando o ambiente sofreu desvios excessivos de configuração e reconstruir uma infraestrutura limpa é o que garante a estabilidade operacional.
Este artigo foi útil?
Deixe uma reação rápida para apoiar o conteúdo:
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários