SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

Como limitar memória e CPU com systemd

Configure MemoryHigh, MemoryMax, CPUQuota e TasksMax na unidade systemd. Veja por que o erro "No section header" remove os limites e como ler o OOM kill.

Limite a memória e a CPU dos processos com um drop-in do systemd

Para limitar a memória e a CPU de um processo numa VPS Linux, adicione algumas linhas à unidade que executa o processo. MemoryMax= é o limite máximo de memória. CPUQuota= é o limite de tempo de processador. Ambos são aplicados pelo cgroup v2 (control groups, versão 2), a funcionalidade do kernel que o systemd já utiliza para contabilizar cada serviço no sistema.

sudo systemctl edit myapp.service

Isto abre um ficheiro drop-in com instruções em comentários. Adicione o seguinte antes dessas instruções:

[Service]
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
TasksMax=128
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl show myapp.service -p MemoryHigh -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMax

systemctl show deve repetir os seus valores nas unidades utilizadas pelo próprio kernel: MemoryMax=805306368 e CPUQuotaPerSecUSec=800ms. Se apresentar MemoryMax=infinity, o drop-in não foi carregado. Verifique se o ficheiro foi criado em /etc/systemd/system/myapp.service.d/override.conf e se começa pelo cabeçalho [Service], porque uma linha de configuração sem uma secção anterior faz o systemd registar Assignment outside of section. Ignoring. e iniciar o serviço sem quaisquer limites.

O restante deste guia explica como escolher esses valores e que problemas ainda podem ocorrer depois de os definir.

Por que um processo descontrolado bloqueia uma VPS sem a encher

Um processo que atinge um limite rígido de memória termina em cerca de um segundo e o serviço é reiniciado. Esse é o caso favorável. O caso problemático é aquele em que nada termina: a máquina responde a ping, o SSH aceita a ligação, mas a linha de comandos nunca aparece. A máquina está ativa e ocupada, mas nenhum desse trabalho é útil.

O mecanismo funciona da seguinte forma. Quando a memória livre fica reduzida, o kernel recupera páginas em vez de alocar novas páginas. As páginas mais fáceis de recuperar são as apoiadas por ficheiros, e a cache de páginas contém o código executável de tudo o que está em execução. Por isso, o kernel remove as páginas de código de sshd, e a instrução seguinte sshd executada provoca uma falha de página que tem de ler novamente esses bytes do armazenamento. Todos os processos acabam por esperar pelo disco em vez de executar. As mesmas páginas são removidas e carregadas repetidamente num ciclo chamado thrashing.

Dois fatores agravam este problema numa VPS em comparação com um portátil. O armazenamento está muitas vezes ligado pela rede ou é partilhado, por isso cada falha custa mais milissegundos do que custaria num dispositivo NVMe local. Além disso, o kernel não mede o tempo; mede as falhas: enquanto a recuperação continuar a devolver uma página, mesmo lentamente, o kernel considera que está a progredir e não chama o out of memory (OOM) killer. Uma máquina pode permanecer nesse estado durante muitos minutos antes de algum processo ser terminado.

Pode observar o problema enquanto ocorre. O kernel disponibiliza informações de pressure stall information (PSI) no Linux 4.20 e posteriores:

cat /proc/pressure/memory
cat /proc/pressure/io
some avg10=63.72 avg60=41.02 avg300=12.33 total=13729481
full avg10=48.15 avg60=30.44 avg300=8.90 total=9114233

A linha full é a relevante. full avg10=48.15 significa que, nos últimos dez segundos, 48% do tempo todas as tarefas executáveis na máquina ficaram bloqueadas à espera de operações de memória, por isso nada foi executado. Num servidor saudável, o valor de full fica próximo de zero. Acima de 10, a lentidão já é percetível para uma pessoa, e 40 ou mais corresponde ao estado que normalmente é descrito como bloqueado.

É também por isso que um limite, por si só, não é uma garantia. Uma unidade sujeita a MemoryHigh= é limitada em vez de ser terminada, por isso continua ativa e lenta, e nada a reinicia porque, do ponto de vista do systemd, ela nunca falhou. Uma unidade limitada que ainda pode usar swap gera leituras e escritas contabilizadas nessa unidade, mas servidas por um único dispositivo partilhado. Assim, pode aumentar /proc/pressure/io para todos os outros serviços da máquina. Os limites determinam quem suporta o custo de uma escassez, mas não criam capacidade.

Verifique se o VPS utiliza cgroup v2

stat -fc %T /sys/fs/cgroup

cgroup2fs é a hierarquia unificada, necessária para todas as definições abaixo. tmpfs indica que o sistema arrancou com o esquema v1 mais antigo, no qual MemoryHigh= e MemorySwapMax= não existem e o comportamento de OOM por unidade é diferente. Ubuntu 22.04 e versões posteriores, bem como Debian 11 e versões posteriores, utilizam v2 por predefinição. Uma imagem antiga ou um kernel arrancado com systemd.unified_cgroup_hierarchy=0 não utiliza v2.

Num sistema com cgroup v2, o systemd ativa por predefinição a contabilização de memória para todas as unidades. Por isso, os valores já estão disponíveis:

systemd-cgtop -m

Este comando apresenta os cgroups ordenados pelo uso de memória. É a forma mais rápida de descobrir o que está a consumir os recursos do servidor enquanto ele ainda responde. Se o servidor for novo, a configuração da conta e da firewall em os primeiros dez minutos num VPS novo deve ser feita antes deste passo.

MemoryHigh limita. MemoryMax encerra.

A diferença entre estas duas definições de memória determina o aspeto de uma falha.

  • MemoryHigh= é um limite flexível. Acima dele, o kernel recupera memória agressivamente desse cgroup e abranda deliberadamente as respetivas alocações. O uso ainda pode ultrapassar o valor, e nenhum processo é encerrado.
  • MemoryMax= é um limite rígido. Quando uma alocação não pode ser satisfeita dentro desse limite, o OOM killer é executado dentro desse cgroup e encerra um dos processos dessa unidade.

Esta segunda parte é o verdadeiro motivo para definir MemoryMax= em qualquer serviço em que não confie totalmente. Sem um limite, uma falta de memória afeta todo o sistema, e o OOM killer global escolhe a vítima com base em oom_score, o que normalmente significa o processo maior. O processo maior costuma ser a base de dados, não o script que provocou a fuga de memória. Com um limite, o processo é encerrado dentro da unidade que causou o problema.

Defina ambos, com MemoryHigh= aproximadamente 20 a 30 por cento abaixo de MemoryMax=. A diferença funciona como uma zona de aviso: uma fuga lenta ultrapassa High e manifesta-se como um serviço que ficou lento, enquanto um pico súbito passa diretamente por Max e provoca o encerramento.

Os valores percentuais são calculados com base na memória física instalada. Por isso, MemoryMax=25% num plano de 4 GB corresponde a 1 GB e continua a representar um quarto do sistema depois de aumentar o plano. MemorySwapMax=0 impede completamente que essa unidade use swap, transformando uma degradação prolongada num encerramento rápido e evidente.

Alguns serviços permitem definir antecipadamente o consumo de memória, em vez de o medir. Uma unidade Ollama dimensiona a respetiva cache KV a partir da janela de contexto que indicar. Leia o custo de aumentar num_ctx em RAM antes de escolher um limite para essa unidade.

Um limite precisa de uma política de reinício associada. Caso contrário, o encerramento deixa apenas um serviço parado.

[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Restart=on-failure
RestartSec=5s

StartLimit* deve estar em [Unit] e Restart= em [Service]. Se colocar qualquer uma das definições na secção errada, o systemd ignora-a. Cinco reinícios em cinco minutos indicam uma fuga, não uma falha momentânea. Depois disso, o systemd desiste e deixa a unidade no estado failed. É esse o estado que pretende encontrar mais tarde, em vez de um ciclo de falhas que oculta o problema.

Limite a CPU com CPUQuota ou partilhe-a com CPUWeight

CPUQuota= usa uma percentagem do tempo disponível num CPU. CPUQuota=50% corresponde a metade de um core. CPUQuota=200% equivale a dois cores, que a unidade pode distribuir pelo número de threads que entender. Num plano com 2 vCPUs, CPUQuota=200% corresponde à máquina inteira.

CPUWeight= é a melhor predefinição para a maioria dos serviços. É uma quota relativa entre 1 e 10000, e o valor predefinido do kernel é 100. Só tem efeito quando existe concorrência: uma tarefa de backup com CPUWeight=20 cede capacidade a um servidor Web com 100 durante a carga, mas continua a usar toda a máquina quando esta está inativa. Uma quota rígida desperdiça essa capacidade disponível.

Seja realista quanto ao que um limite de CPU oferece. Um processo limitado pela CPU raramente bloqueia o Linux, porque o escalonador continua a atribuir tempo a todos os processos. É a memória que pode fazer uma máquina deixar de responder. Use CPUQuota= quando precisar de um limite previsível, por exemplo numa compilação ou num agente que, de outro modo, usaria toda a CPU durante uma hora. Dimensionar esse tipo de carga é uma questão separada, abordada em quanta RAM e CPU um VPS para um agente de programação precisa.

Se a CPU aparecer como ocupada quando nenhum dos seus processos estiver a fazer muito, a causa pode estar do outro lado do hipervisor. Trata-se de tempo de CPU steal causado por um vizinho ruidoso, e nenhum limite que defina alterará essa situação.

TasksMax impede um loop de fork

TasksMax= é o número de processos e threads que uma unidade pode manter. As threads contam, por isso um serviço Java ou Go precisa de mais margem do que a lista de processos sugere. Esta é a proteção mais simples contra um script que executa fork num loop, porque o fork falha dentro da unidade em vez de o servidor ficar sem IDs de processos.

TasksMax=128

Quando uma unidade atinge o limite, o kernel regista uma linha que identifica o cgroup:

cgroup: fork rejected by pids controller in /system.slice/myapp.service

O próprio programa normalmente comunica fork: retry: Resource temporarily unavailable. Verifique o que o gestor aplica por predefinição com systemctl show -p DefaultTasksMax.

Limite um trabalho pontual com systemd-run

Não precisa de um ficheiro de unidade para usar nada disto. systemd-run cria uma unidade transitória à volta de um único comando.

sudo systemd-run --scope -p MemoryMax=1G -p MemorySwapMax=0 -p CPUQuota=50% -p TasksMax=64 ./import-data.sh

--scope executa o comando no seu terminal, depois de apresentar Running scope as unit: run-r7c1a....scope. O resultado permanece no ecrã e os limites desaparecem quando o comando termina. Qualquer propriedade de systemd.resource-control funciona depois de -p.

Para um trabalho demorado, remova --scope e atribua-lhe um nome. O trabalho é executado em segundo plano como um serviço transitório e regista os eventos no journal:

sudo systemd-run --unit=nightly-import -p MemoryMax=1G -p CPUWeight=20 ./import-data.sh
journalctl -u nightly-import -f

As mesmas opções funcionam com --user quando não é root, embora o gestor do utilizador tenha apenas os controladores que lhe foram delegados, pelo que uma propriedade pode ser rejeitada nesse contexto. Execute-o com sudo se isso acontecer. Quando um trabalho passa a ter uma configuração permanente, as definições são transferidas sem alterações para uma unidade real: consulte executar um script como serviço e temporizador do systemd.

A questão da swap, respondida com honestidade

A swap altera a forma como a falha ocorre, mas não a impede.

Sem swap, uma fuga de memória atinge o limite e algum processo morre em poucos segundos. A indisponibilidade é evidente, curta e fácil de analisar depois no journal. Com swap, o kernel escreve no disco as páginas anónimas pouco usadas e ganha tempo. Se o processo ia estabilizar, a swap ajuda. Se for um processo descontrolado, a swap transforma uma indisponibilidade de cinco segundos numa lentidão de vinte minutos. Essa lentidão é pior, porque um processo morto ainda deixa um shell funcional, enquanto um sistema em thrashing não.

swapon --show
free -h

Uma abordagem equilibrada num VPS pequeno é manter um ficheiro de swap moderado para páginas que são alocadas uma vez e nunca mais são acedidas, e definir MemorySwapMax=0 nas unidades que aceita perder. Os serviços importantes mantêm acesso à swap. Os serviços imprevisíveis atingem rapidamente o limite e reiniciam.

Reduzir vm.swappiness é uma medida pouco eficaz, e é importante perceber porquê. Apenas altera o equilíbrio entre remover páginas da cache de páginas e trocar páginas anónimas para a swap. Ambas as opções podem exigir uma leitura do disco mais tarde. A alteração determina que páginas entram em thrashing, mas não evita o thrashing do sistema.

Um daemon OOM antecipado mata processos antes da paragem

O kernel espera que a recuperação de memória falhe completamente. Numa VPS pequena, essa espera é precisamente o intervalo em que perde a máquina. Dois daemons em espaço de utilizador reduzem esse intervalo ao monitorizarem a memória e matarem processos mais cedo.

earlyoom monitoriza a memória disponível e o swap livre. Mata o processo com a pontuação mais elevada quando um dos valores fica abaixo de um limite.

sudo apt install earlyoom
systemctl status earlyoom

O pacote Debian e Ubuntu inicia o serviço durante a instalação. As opções ficam em /etc/default/earlyoom:

EARLYOOM_ARGS="-m 5,2 -s 5,2 --avoid '^(sshd|systemd)$' --prefer '^(node|python3)$'"

-m PERCENT define o mínimo de memória disponível e -s PERCENT o mínimo de swap livre. Ambos têm o valor predefinido de 10 por cento. O segundo número de cada par é o limite para SIGKILL: o earlyoom envia SIGTERM quando o valor fica abaixo do primeiro limite e SIGKILL quando fica abaixo do segundo. Por predefinição, o segundo limite corresponde a metade do primeiro. Aplique uma alteração com sudo systemctl restart earlyoom. Leia journalctl -u earlyoom para ver que processo foi terminado e quanta memória esse processo mantinha.

systemd-oomd é a outra opção. A respetiva página de manual descreve-o como "um serviço do sistema que utiliza cgroups-v2 e informações de pressão de stall (PSI) para monitorizar e tomar medidas corretivas antes de ocorrer um OOM no espaço do kernel". Atua sobre cgroups completos, e não sobre processos individuais. Por isso, termina uma unidade, e não um processo filho isolado. As unidades optam por utilizá-lo com ManagedOOMMemoryPressure=kill ou ManagedOOMSwap=kill. Os limites ficam em /etc/systemd/oomd.conf.

systemctl status systemd-oomd
oomctl

oomctl mostra o que está a monitorizar atualmente. Muitas vezes, não mostra nada numa imagem de servidor, porque a configuração é opcional para cada unidade. Escolha um daemon e use apenas esse. Executar ambos significa que existem duas ferramentas a competir para escolher uma vítima. Isso também dificulta determinar posteriormente o motivo de cada término.

Que unidade foi responsável?

Comece pelo kernel, porque ele regista cada processo que termina.

journalctl -k --grep "Killed process" --since "2 hours ago"

Um kill feito pelo OOM killer global aparece assim:

Out of memory: Killed process 4127 (node) total-vm:2731084kB, anon-rss:1874232kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:4212kB oom_score_adj:0

anon-rss é a memória que o processo mantinha na RAM quando terminou, cerca de 1.8 GB neste caso. Leia o nome entre parênteses com cautela. Esse é o processo vítima escolhido pelo kernel, e o kernel escolhe o maior processo. Nem sempre é o processo que causou a falta de memória.

Um kill causado por um limite de cgroup tem um prefixo diferente, e o relatório apresentado acima dele identifica o cgroup que atingiu o seu próprio limite:

Memory cgroup out of memory: Killed process 8811 (python3) total-vm:1044320kB, anon-rss:769112kB, file-rss:0kB, shmem-rss:0kB, UID:998 pgtables:1720kB oom_score_adj:0

Esse prefixo fornece a maior parte do diagnóstico. Memory cgroup out of memory significa que uma unidade atingiu o MemoryMax= que lhe foi atribuído e que o resto do servidor estava normal. Um Out of memory simples significa que a máquina inteira ficou sem memória. Nesse caso, os seus limites estavam ausentes ou eram demasiado generosos quando somados.

Depois, pergunte ao systemd o que ele registou:

systemctl status myapp.service
journalctl -u myapp.service -n 50
myapp.service: A process of this unit has been killed by the OOM killer.
myapp.service: Main process exited, code=killed, status=9/KILL
myapp.service: Failed with result 'oom-kill'.

systemctl status indica a mesma situação numa linha, como Active: failed (Result: oom-kill).

Os contadores do cgroup são a terceira fonte e a única que regista a limitação de recursos, que nunca produz uma linha de log:

cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
cat /sys/fs/cgroup/system.slice/myapp.service/memory.peak
low 0
high 4213
max 118
oom 12
oom_kill 12

high conta quantas vezes a unidade ultrapassou MemoryHigh= e foi limitada. max conta quantas vezes atingiu o limite rígido, e oom_kill conta os processos efetivamente terminados. Um high elevado com oom_kill 0 é o caso silencioso descrito anteriormente: o serviço continua em execução, fica extremamente lento e não comunicou nenhuma falha. memory.peak (Linux 5.19 e versões posteriores) guarda o maior consumo atingido pelo cgroup. Esse é o valor que deve usar para dimensionar MemoryMax=. Ambos os ficheiros são repostos quando a unidade reinicia, porque o systemd cria novamente o cgroup.

Existe um pré-requisito subjacente a tudo isto. Se /var/log/journal não existir, o journal fica na RAM e todas as linhas desaparecem depois do reboot necessário para recuperar o servidor.

sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --list-boots

Se journalctl --list-boots mostrar mais do que o boot atual, o histórico passou a ser persistente. Assim, journalctl -k -b -1 pode mostrar as mensagens do kernel relativas ao boot que terminou por falha.

Um ponto de partida para um VPS pequeno

Num plano de 2 GB, reserve 300 a 400 MB para o kernel e a cache de páginas. Não deixe que os limites somem os 2 GB completos, porque todas as unidades podem atingir o pico ao mesmo tempo. Dê ao serviço mais importante a maior parcela. Depois, limite tudo o que for secundário à sua volta.

[Service]
MemoryHigh=256M
MemoryMax=384M
MemorySwapMax=0
CPUWeight=20
TasksMax=64
Restart=on-failure
RestartSec=5s

Manter uma forma de acesso vale mais uma configuração. OOMScoreAdjust=-500 num drop-in para ssh.service torna muito menos provável que o OOM killer global escolha o daemon SSH como vítima. Isso pode ser a diferença entre corrigir o servidor e reiniciá-lo no painel de controlo. A alteração afeta apenas a escolha da vítima pelo kernel. Não reduz a duração da paragem.

Os contentores são executados nos seus próprios cgroups. Estes cgroups são criados pelo runtime de contentores, e não pelos ficheiros das suas unidades. Por isso, um limite em docker.service não se transforma num limite para um contentor individual. Os equivalentes por contentor de MemoryMax= e CPUQuota= são abordados em configurar limites de memória e CPU no Docker Compose.

FAQ

Por que meu VPS congelou em vez de encerrar o processo descontrolado?

Porque o kernel avalia o progresso verificando se a recuperação devolve páginas, e não quanto tempo isso demora. Quando há pouca memória, ele remove o cache de páginas, incluindo as páginas executáveis de programas em execução, e depois as lê novamente na instrução seguinte. Tudo fica aguardando o armazenamento e nenhuma alocação falha tecnicamente, portanto o OOM killer nunca é chamado. Consulte /proc/pressure/memory enquanto isso acontece: um full avg10 acima de 40 significa que quase nenhuma tarefa conseguiu executar nos últimos dez segundos. Um daemon em userspace, como earlyoom, encerra o processo antes de o servidor chegar a esse estado.

Qual é a diferença entre MemoryHigh e MemoryMax?

MemoryHigh= é um limite flexível que impõe contenção. O kernel recupera memória agressivamente da unidade e reduz a velocidade das alocações, mas o uso pode ultrapassar o número e nenhum processo é encerrado. MemoryMax= é um limite rígido: uma alocação que não pode ser atendida dentro desse limite chama o OOM killer no cgroup da própria unidade. Assim, o processo que causou o problema é encerrado, em vez do maior processo do servidor. Defina MemoryHigh= abaixo de MemoryMax= e trate a diferença entre ambos como uma zona de aviso.

Como descubro qual serviço foi atingido pelo OOM killer?

Execute journalctl -k --grep "Killed process" --since "2 hours ago". Uma linha que começa com Memory cgroup out of memory significa que uma unidade atingiu o próprio MemoryMax=, enquanto um Out of memory simples significa que toda a máquina ficou sem memória. Em seguida, execute journalctl -u <unit> -n 50 e procure Failed with result 'oom-kill'. Se /var/log/journal não existir no seu servidor, o journal foi mantido na RAM e as evidências foram perdidas com o reboot. Crie esse diretório antes do próximo incidente.

Devo adicionar swap a um VPS pequeno?

Um arquivo de swap pequeno ajuda com páginas frias que são alocadas uma vez e nunca mais acessadas. Ele não ajuda com um processo descontrolado: atrasa o encerramento e transforma uma interrupção curta numa paralisação longa, durante a qual não é possível iniciar sessão para corrigir o problema. Mantenha o swap em tamanho moderado e defina MemorySwapMax=0 nas unidades que podem ser perdidas. Assim, elas atingem o limite e reiniciam rapidamente, enquanto os serviços importantes continuam usando o próprio swap.

Posso limitar um comando sem escrever um arquivo de unidade?

Sim. sudo systemd-run --scope -p MemoryMax=1G -p CPUQuota=50% ./script.sh executa o comando no seu terminal, dentro de um escopo transitório com esses limites. Os limites desaparecem quando o comando termina. Todas as propriedades de systemd.resource-control ficam disponíveis depois de -p, portanto MemorySwapMax=, TasksMax= e CPUWeight= também funcionam nesse contexto. Remova --scope e adicione --unit=name para executar o trabalho em segundo plano, com a saída no journal.