Como estabilizei uma VPS travando com swap lotado, Wazuh, Kiro, Codex e sessões SSH caindo#
Fala, pessoal. Hoje o dia foi tenso. Eu estava no flow, codando um projeto que vou chamar aqui de Evolya, quando a conexão com o servidor começou a cair do nada. Não era só o editor remoto desconectando. A sensação era de que a VPS inteira resolvia tirar um cochilo a cada alguns minutos.
Eu estava usando dois editores remotos simultâneos via SSH, o Kiro e o Codex. A cada 10 minutos vinha o mesmo soco no estômago: conexão perdida, sessão travada, terminal sem resposta e aquela dúvida clássica de infraestrutura, "foi rede, foi SSH ou o servidor está morrendo por dentro?".
Nota explicativa: Kiro e Codex são editores de código com suporte a inteligência artificial (IA) que rodam processos no servidor remoto via protocolo SSH. Eles iniciam servidores de linguagem (Language Server Protocol - LSP), watchers de arquivos do sistema e processos auxiliares de indexação que consomem recursos de memória RAM e CPU significativos.
Como trabalho com suporte e infraestrutura há bastante tempo, esse tipo de sintoma acende um alerta específico. Queda recorrente de SSH durante carga de desenvolvimento remoto raramente é apenas "internet ruim". Muitas vezes é pressão de memória, swap saturado, I/O travado, processo Java consumindo heap, node indexando projeto em duplicidade ou kernel gastando energia tentando sobreviver.
Então eu parei de tratar como desconexão de editor e fiz a autópsia da VPS.
O sintoma operacional#
O padrão era este:
- Kiro desconectava via SSH;
- Codex também perdia sessão;
- terminais ficavam lentos ou sem resposta;
- a VPS não caía completamente de imediato, mas ficava com comportamento de travamento;
- depois de alguns minutos, a sessão às vezes voltava, às vezes exigia reconectar;
- o problema repetia a cada ciclo de carga, especialmente com projeto aberto e indexação ativa.
O projeto tinha muitos arquivos, dependências Node e atividade de linguagem. Isso importa porque editores remotos não são apenas "uma janela bonita". Eles sobem processos no servidor, indexam arquivos, rodam LSP, TypeScript Server, watchers, extensões, caches e tarefas auxiliares.
Com dois editores simultâneos, esse custo dobra ou quase dobra.
Diagnóstico forense: investigando o OOM killer#
Quando um servidor web ou VPS começa a apresentar travamentos intermitentes e lentidão extrema sob estresse de memória, o primeiro suspeito a ser investigado é o OOM (Out Of Memory) Killer do kernel do Linux. Ele atua eliminando processos pesados para salvar o sistema de um pânico geral.
Para auditar se o OOM Killer foi ativado e quais serviços foram sacrificados:
# Buscar registros de processos abortados por falta de memória no buffer de mensagens do kernel
dmesg | grep -i "oom\|out of memory"
# Investigar o log de sistema syslog
grep -i "oom\|out of memory" /var/log/syslog
# Verificar processos mortos via journalctl
journalctl -k | grep -i "oom\|killed process"
# Verificar se há processos no sistema em estado zumbi ou terminando
ps aux | grep -i "killed"
Auditoria de desempenho de disco (i/o bottlenecks)#
O travamento das sessões SSH muitas vezes decorre da contenção de I/O de disco (disk thrashing) durante as operações de swap agressivo. Para validar se o disco SSD/NVMe da VPS está saturado:
# Monitorar estatísticas estendidas de leitura e escrita do disco (atualizado a cada 1 segundo)
iostat -x 1 5
# Monitorar em tempo real quais processos estão gerando mais I/O de escrita e leitura
iotop -o
# Listar processos agrupados por consumo detalhado de I/O
sudo iotop -oP
# Verificar estatísticas de memória virtual e taxas de paginação (si/so - swap in / swap out)
vmstat 1 10
# Verificar se existem processos ativamente bloqueados esperando operações de I/O de disco
cat /proc/stat | grep procs_blocked
A primeira medição: memória e SWAP#
A VPS tinha 12GB de RAM. Em teoria, parecia folgado para desenvolvimento e serviços auxiliares. Mas quando rodei:
free -g
o resultado explicou muita coisa:
total used free shared buff/cache available
Mem: 11 7 2 0 1 3
Swap: 1 1 0
O ponto crítico estava no swap:
Swap: 1GB total, 1GB usado, 0GB livre
O swap estava 100% entupido.
Com 12GB de RAM, manter apenas 1GB de swap é muito apertado para um servidor que roda Wazuh/OpenSearch, processos Node de editores remotos e projeto grande sendo indexado. Quando a RAM começa a encostar no limite e o swap já está cheio, o Linux entra em um cenário muito ruim: ele tenta mover páginas de memória entre RAM e disco, mas não tem margem suficiente para respirar.
O que acontece quando o SWAP lota#
Quando o Linux fica sem memória física confortável e sem swap livre, ele pode entrar em comportamento de pressão extrema. Um dos efeitos práticos é o disk thrashing.
Na prática:
- o kernel tenta paginar memória para o disco;
- processos competem por RAM;
- o SSD começa a receber leitura/escrita de páginas;
- o CPU gasta tempo esperando I/O;
- processos interativos, como SSH, ficam sem resposta;
- editores remotos perdem keepalive;
- o servidor parece "congelado", mesmo sem reboot.
Abaixo está o fluxo visual da cascata de degradação sob pressão extrema de memória:
Esse detalhe é importante. O SSH não precisa estar configurado errado para cair. Ele pode cair porque o servidor fica tempo demais sem conseguir responder. Se o processo sshd, o shell, o terminal remoto ou o processo do editor não conseguem agendar CPU e I/O a tempo, a conexão parece morrer.
Encontrando os culpados#
Depois da leitura de memória, fui procurar quem estava consumindo mais RAM.
Um comando prático para isso é:
ps aux --sort=-%mem | head -20
Também gosto de usar:
top
ou:
htop
quando disponível.
Os principais vilões eram previsíveis, mas a combinação deles era o problema real.
Wazuh indexer: java/opensearch comendo memória#
O Wazuh Indexer, baseado em OpenSearch/Java, estava consumindo quase 2GB de RAM sozinho. O heap estava configurado com 1GB fixo, mas o processo Java não vive apenas de heap. Existe overhead de JVM, threads, buffers, mmap, cache, estruturas internas e o próprio custo operacional do indexador.
Configurando o JVM heap do Wazuh indexer#
Para servidores de desenvolvimento compartilhados ou VPS com recursos limitados, reduzir a alocação máxima de memória heap da máquina virtual Java (JVM) é vital para evitar travamentos de outros processos essenciais.
- Abra o arquivo de opções de configuração da JVM do indexador:
sudo nano /etc/wazuh-indexer/jvm.options
- Reduza os limites mínimo (
-Xms) e máximo (-Xmx) de memória alocada (por exemplo, definindo 512MB para ambientes pequenos):
-Xms512m
-Xmx512m
- Caso aplicável, revise parâmetros gerais de memória no arquivo de configuração do indexador:
sudo nano /etc/wazuh-indexer/opensearch.yml
- Reinicie o serviço do indexador para aplicar os novos limites:
sudo systemctl restart wazuh-indexer
- Verifique o consumo real de memória do processo após o reinício:
ps aux | grep wazuh | grep -v grep
Ferramentas como Wazuh são excelentes, mas em VPS pequena ou média elas precisam ser dimensionadas com carinho. O Java reserva memória com vontade, e o OpenSearch gosta de estabilidade, disco rápido e RAM previsível.
Em um servidor onde eu também estou desenvolvendo com editores remotos, o Wazuh não concorre apenas com serviços de produção. Ele concorre com:
- processos
node; - TypeScript Server;
- watchers de arquivos;
- extensões de editor;
- shells remotos;
- indexação de projeto;
- cache de dependências;
- processos de build.
Kiro e codex: Node.js e tsserver duplicados#
O segundo grupo de consumo vinha dos editores remotos. Kiro e Codex, usados via SSH, levantam processos no servidor. Dependendo do projeto, eles iniciam servidores de linguagem e indexadores que analisam a árvore de arquivos.
Em projeto Node/TypeScript, isso geralmente envolve:
node;tsserver;- watchers;
- análise de
package.json; - varredura de diretórios;
- cache de símbolos;
- extensões auxiliares;
- processos remotos persistentes.
Com dois editores abertos no mesmo projeto, eu tinha instâncias duplicadas fazendo trabalho parecido. O que parece "só abrir outro editor" vira custo real de memória e CPU.
Impondo limites de memória para processos Node.js#
Como os processos do Language Server (LSP) e indexadores do TypeScript Server rodando sob o Node.js podem se expandir indefinidamente e consumir toda a memória RAM, é prudente aplicar limites de tamanho máximo de memória (heap size) para a runtime do Node.
- Limitar o consumo máximo em variáveis de ambiente globais de sessão:
export NODE_OPTIONS="--max-old-space-size=2048" # Limita a 2GB
- Ou limitar individualmente ao disparar scripts ou serviços de background:
node --max-old-space-size=1024 app.js # Limita a 1GB
- Para ambientes multiusuários com isolamento estrito, configure grupos de controle do kernel (cgroups):
# Criar o cgroup de memória
sudo cgcreate -g memory:/node-limit
# Definir o teto de consumo de RAM do grupo para 2GB
sudo cgset -r memory.limit_in_bytes=2G node-limit
# Executar o processo restrito ao cgroup criado
sudo cgexec -g memory:node-limit node app.js
Esse foi o conselho de vilões do incidente:
Wazuh Indexer + Kiro + Codex + Node/TSServer + swap pequeno
Solução 1: Upgrade urgente do SWAP#
O primeiro passo foi dar fôlego para o servidor.
Um swap de 1GB para uma VPS com 12GB de RAM e esse perfil de carga era pouco. Eu subi para 8GB, usando arquivo de swap.
Backup de arquivos críticos do sistema#
Antes de editar qualquer ponto de montagem ou arquivos do kernel do Linux, faça backups de segurança. Um erro de sintaxe no arquivo /etc/fstab impedirá o boot do servidor remoto, resultando em lockout administrativo.
# Criar backup com timestamp do fstab
sudo cp /etc/fstab /etc/fstab.bak.$(date +%Y%m%d)
# Criar backup completo e compactado de todas as configurações críticas de sistema
sudo tar czf /root/system-backup-$(date +%Y%m%d).tar.gz /etc/fstab /etc/sysctl.conf /etc/ssh/sshd_config
Primeiro desativei o swap antigo:
sudo swapoff -a
Depois criei um novo arquivo de 8GB:
sudo fallocate -l 8G /swapfile_novo
sudo chmod 600 /swapfile_novo
sudo mkswap /swapfile_novo
sudo swapon /swapfile_novo
O chmod 600 é obrigatório por segurança. O arquivo de swap /swapfile_novo não deve ser legível por qualquer usuário, porque pode conter páginas de memória de processos.
Depois eu valido com:
free -h
swapon --show
O objetivo não é transformar swap em RAM principal. O objetivo é dar margem para o kernel sobreviver a picos sem travar a VPS.
Persistência do SWAP#
Para tornar permanente, eu valido a entrada no /etc/fstab. O formato típico seria:
/swapfile_novo none swap sw 0 0
Antes de reiniciar uma VPS remota, eu sempre valido sintaxe com cuidado, porque erro em /etc/fstab pode atrapalhar boot.
Também posso testar:
sudo mount -a
swapon --show
No caso de swap, o mais importante é garantir que ele suba após reboot e que o swap antigo não volte duplicado, se não for mais desejado.
Solução 2: Tunando swappiness#
Depois de criar swap, eu ajustei o comportamento do kernel.
Não adianta ter swap maior se o sistema usar disco de forma agressiva quando ainda existe RAM útil. Para esse tipo de VPS, eu prefiro o kernel usando swap como última linha de defesa, não como rotina.
Apliquei:
sudo sysctl vm.swappiness=10
E deixei permanente:
echo 'vm.swappiness=10' >> /etc/sysctl.conf
O vm.swappiness=10 orienta o kernel a priorizar RAM e só usar swap de forma mais conservadora. Não significa "nunca usar swap", mas reduz a tendência de paginar cedo demais.
Depois valido:
sysctl vm.swappiness
Cuidado com duplicidade no sysctl.conf#
Um detalhe operacional: antes de sair dando append no /etc/sysctl.conf, eu gosto de verificar se a diretiva já existe:
grep -n '^vm.swappiness' /etc/sysctl.conf
Se existir, eu edito a linha. Se não existir, adiciono. Isso evita múltiplas linhas conflitantes no arquivo.
Em ambiente controlado, também posso criar um arquivo dedicado em /etc/sysctl.d/99-vps-memory.conf com:
vm.swappiness=10
Mas no atendimento descrito eu usei o caminho direto no /etc/sysctl.conf, porque precisava estabilizar rápido.
Solução 3: Ajuste fino no SSH#
Com memória estabilizada, eu ajustei o SSH para ser mais tolerante a períodos curtos de carga e limitei conexões não autenticadas simultâneas.
Verificação de logs do SSH para depuração#
Para diagnosticar se conexões caíram por timeouts ou falhas de autenticação de sockets, analise os logs do daemon sshd:
# Verificar últimas mensagens do daemon SSH
sudo tail -100 /var/log/auth.log | grep sshd
# Buscar especificamente por mensagens de timeout, desconexões e resets de conexões
sudo grep -i "disconnect\|timeout\|reset" /var/log/auth.log | tail -20
Ajustando parâmetros no sshd_config#
No arquivo /etc/ssh/sshd_config usei:
TCPKeepAlive yes
ClientAliveInterval 60
ClientAliveCountMax 3
MaxSessions 50
MaxStartups 10:30:60
O que isso faz na prática:
TCPKeepAlive yes: mantém keepalive TCP ativo;ClientAliveInterval 60: o servidor envia mensagem de keepalive a cada 60 segundos;ClientAliveCountMax 3: permite até 3 tentativas sem resposta antes de considerar a sessão morta;MaxSessions 50: aumenta margem para múltiplas sessões multiplexadas ou editores remotos;MaxStartups 10:30:60: limita conexões concorrentes não autenticadas para mitigar ataques de negação de serviço e sobrecarga de autenticação (10 conexões com início de descarte aleatório em 30% até o teto de 60 conexões).
Depois de editar, eu sempre valido a configuração antes de reiniciar:
sudo sshd -t
Se não houver erro, aplico:
sudo systemctl restart sshd
ou, dependendo da distro:
sudo systemctl restart ssh
Esse cuidado é importante porque erro no /etc/ssh/sshd_config em VPS remota pode te trancar fora do servidor.
Por que SSH keepalive não resolve SWAP lotado#
Esse ponto é importante. Ajustar SSH ajuda, mas não resolve servidor travado por memória.
Se a VPS entra em thrashing severo, o sshd pode nem conseguir responder aos keepalives. Então o ajuste de SSH é uma camada de tolerância, não a cura da causa raiz.
A ordem correta foi:
estabilizar memória -> ajustar comportamento do kernel -> ajustar SSH
Se eu fizesse só SSH, as quedas continuariam quando a VPS ficasse sem fôlego.
Limpando processos remotos zumbis#
Outra lição foi com os processos de editores remotos. Quando uma sessão cai, às vezes sobram processos no servidor.
Dependendo do editor, dá para encontrar processos como:
ps aux | grep -Ei 'vscode-server|node|tsserver|kiro|codex'
E, se necessário, limpar processos pendurados:
pkill -f vscode-server
ou com filtro específico para o processo problemático.
Eu uso isso com cuidado para não matar processo de build, aplicação Node legítima ou serviço em produção. O ideal é revisar antes com ps.
Inotify watches em projetos grandes#
Outro ponto importante para projetos com muitos arquivos é o limite de watchers do inotify.
Projetos Node com node_modules, monorepos, caches e muitos arquivos podem estourar limites do sistema. Editores remotos e ferramentas de build dependem de watchers para detectar mudanças em arquivos.
O ajuste emergencial é:
sudo sysctl -w fs.inotify.max_user_watches=524288
Para persistir, eu posso adicionar em sysctl:
fs.inotify.max_user_watches=524288
Esse ajuste não reduz consumo de RAM por si só, mas evita erros e loops estranhos de ferramentas tentando monitorar projeto grande sem limite suficiente.
Verificação e auditoria de limites do sistema#
Além dos watchers de arquivos, é fundamental verificar outros limites operacionais do sistema sob alta concorrência:
# Verificar limites de recursos atribuídos à sessão (ulimits)
ulimit -a
# Verificar o limite máximo de Process IDs (PIDs) permitidos no kernel
cat /proc/sys/kernel/pid_max
# Verificar o limite máximo de threads do sistema
cat /proc/sys/kernel/threads-max
# Verificar limite geral de descritores de arquivos abertos (file-max)
cat /proc/sys/fs/file-max
# Auditar configurações de overcommit de memória do kernel
cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/overcommit_ratio
Ajuste de limites globais via systemd#
Para garantir que limites de conexões simultâneas e PIDs de serviços do sistema não travem processos sob cargas pesadas, edite as configurações do gerenciador do Systemd:
# Editar limites globais do systemd
sudo nano /etc/systemd/system.conf
# Adicionar ou descomentar as linhas de limites recomendados
# [Manager]
# DefaultLimitNOFILE=65536
# DefaultLimitNPROC=65536
# Recarregar as configurações de execução do Systemd sem reiniciar o servidor
sudo systemctl daemon-reexec
Verificação pós-reboot e monitoramento contínuo#
Sempre valide se todas as diretivas de otimização persistem após uma reinicialização da VPS.
Auditoria pós-reboot#
# 1. Verificar se o swapfile subiu corretamente e a quantidade de RAM livre
swapon --show
free -h
# 2. Confirmar se as configurações de swappiness e inotify persistiram
sysctl vm.swappiness
sysctl fs.inotify.max_user_watches
# 3. Testar se a sintaxe do SSH daemon está correta e keepalive está configurado
sshd -T | grep -i "keepalive\|session\|maxstartups"
# 4. Validar se o fstab não contém erros de sintaxe montando os pontos ativamente
sudo mount -a # Se o comando retornar sem saída de erro, está correto
Configurando o monitoramento contínuo de recursos#
Para garantir que a VPS permaneça estável sob picos de tráfego, implemente monitoramento de recursos em tempo real e de longo prazo:
# Monitorar estatísticas de consumo de RAM livre em tempo real
watch -n 1 free -h
# Acompanhar atividade de paginação de swap do sistema
watch -n 1 swapon --show
# Listar os 10 processos que mais consomem memória atualizado dinamicamente
watch -n 1 "ps aux --sort=-%mem | head -10"
# Instalar pacotes de monitoramento de performance essenciais
sudo apt install htop iotop sysstat -y
# Ativar e iniciar o daemon de monitoramento contínuo sysstat para histórico de logs
sudo systemctl enable sysstat
sudo systemctl start sysstat
O roteiro de guerra que usei#
Meu fluxo de estabilização ficou assim:
- Medir memória e swap:
free -g
swapon --show
- Identificar processos mais pesados:
ps aux --sort=-%mem | head -20
- Criar swap maior:
sudo swapoff -a
sudo fallocate -l 8G /swapfile_novo
sudo chmod 600 /swapfile_novo
sudo mkswap /swapfile_novo
sudo swapon /swapfile_novo
- Ajustar swappiness:
sudo sysctl vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.conf
- Ajustar SSH:
TCPKeepAlive yes
ClientAliveInterval 60
ClientAliveCountMax 3
MaxSessions 50
MaxStartups 10:30:60
- Validar SSH antes de reiniciar:
sudo sshd -t
- Aumentar inotify se necessário:
sudo sysctl -w fs.inotify.max_user_watches=524288
- Limpar processos remotos zumbis quando aplicável:
ps aux | grep -Ei 'vscode-server|node|tsserver|kiro|codex'
pkill -f vscode-server
Checklist: estabilização de VPS com problemas de memória#
Diagnóstico#
- [ ] Verificar memória:
free -h - [ ] Verificar swap:
swapon --show - [ ] Identificar processos:
ps aux --sort=-%mem | head -20 - [ ] Verificar I/O:
iostat -x 1 5 - [ ] Verificar OOM killer:
dmesg | grep -i oom
Correção - memória#
- [ ] Backup do fstab:
cp /etc/fstab /etc/fstab.bak - [ ] Criar swap maior:
fallocate -l 8G /swapfile - [ ] Configurar permissões:
chmod 600 /swapfile - [ ] Ativar swap:
mkswap /swapfile && swapon /swapfile - [ ] Persistir no
/etc/fstab
Correção - kernel#
- [ ] Ajustar swappiness:
sysctl vm.swappiness=10 - [ ] Ajustar inotify:
sysctl fs.inotify.max_user_watches=524288 - [ ] Persistir em
/etc/sysctl.confou/etc/sysctl.d/
Correção - SSH#
- [ ] Validar config:
sshd -t - [ ] Ajustar keepalive:
ClientAliveInterval 60 - [ ] Configurar conexões concorrentes:
MaxStartups 10:30:60 - [ ] Reiniciar:
systemctl restart sshd
Pós-correção#
- [ ] Verificar memória:
free -h - [ ] Verificar swap:
swapon --show - [ ] Verificar limite do inotify:
sysctl fs.inotify.max_user_watches - [ ] Testar conexão SSH
- [ ] Monitorar por 24h
Resultado depois dos ajustes#
Depois dessas mudanças, a VPS parou de negar fogo. O uso de RAM estabilizou, o swap deixou de ficar completamente sem margem, e as sessões remotas pararam de cair em sequência.
O ponto principal é que eu não tratei a desconexão do SSH como causa. Eu tratei como sintoma. A causa estava na combinação de swap minúsculo, pressão de memória, Wazuh Indexer com Java/OpenSearch, editores remotos duplicando processos Node/TSServer e limites do sistema que não estavam dimensionados para aquele modo de trabalho.
No fim, a correção foi dar fôlego ao kernel, reduzir agressividade de swap, manter SSH mais tolerante e entender que dois editores remotos simultâneos têm custo real. É aquele tipo de ajuste que parece simples depois de pronto, mas que exige leitura operacional: medir, identificar gargalos e dimensionar a casa para o trabalho que ela precisa aguentar.
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