---
title: "A falsa segurança de rodar serviços sem root ignorando as diretivas do systemd"
author: "Pedro Dalben"
date: "2026-10-06T11:02:43Z"
category: "DevOps & Cloud"
reading_time: "7 min"
canonical: "https://pedrodalben.com.br/blog/a-falsa-seguranca-de-rodar-servicos-sem-root-ignorando-as-diretivas-do-systemd"
---

# A falsa segurança de rodar serviços sem root ignorando as diretivas do systemd

> Criar um usuário sem sudo não blinda sua VPS se o processo continuar lendo chaves SSH, configs alheias e gravando onde não deve.

**Autor:** Pedro Dalben (CTO no Integrar Plus)  
**Publicado em:** 06 de October de 2026  
**URL Canônica Oficial:** [https://pedrodalben.com.br/blog/a-falsa-seguranca-de-rodar-servicos-sem-root-ignorando-as-diretivas-do-systemd](https://pedrodalben.com.br/blog/a-falsa-seguranca-de-rodar-servicos-sem-root-ignorando-as-diretivas-do-systemd)  

---


Muita gente sobe uma aplicação em VPS acreditando que cumpriu a cartilha de segurança ao criar um usuário dedicado, digamos `deploy` ou `rails`, sem privilégios de sudo. A lógica parece confortável: se o processo for comprometido por uma execução remota de código ou falha de upload, o invasor não é root, logo o estrago estaria contido.

Essa premissa cai por terra na primeira inspeção real do sistema de arquivos.

No Linux padrão, qualquer usuário sem privilégios consegue ler uma quantidade absurda de diretórios. Ele navega por `/home`, lê arquivos em `/tmp`, inspeciona processos em `/proc` e, frequentemente, consegue abrir arquivos de configuração esquecidos com permissão `644` contendo credenciais de banco ou tokens de API. Se você roda seus workers do Sidekiq ou a aplicação Puma sob um usuário comum sem restringir a superfície com o próprio init do sistema, você entregou a máquina inteira para leitura.

Existe uma corrente que defende empacotar absolutamente tudo em Docker para resolver isso com namespaces. Outra defende que permissões POSIX tradicionais bastam se você configurar `chmod` e `chown` manualmente em cada pasta. Eu discordo das duas abordagens em servidores dedicados ou instâncias virtuais enxutas. 

O Docker adiciona uma camada de rede virtual, daemon rodando com privilégios elevados e complexidade de orquestração que muitas vezes mascara o problema básico de permissão em disco. Já gerenciar permissões POSIX no braço é frágil: basta uma atualização de pacote, uma automação de deploy desatenta ou um arquivo criado dinamicamente para abrir brechas no filesystem.

A ferramenta correta para estancar esse vazamento já está rodando no seu Linux e raramente é usada no seu potencial: o systemd.

## O abismo entre permissão de usuário e isolamento de processo

Quando um processo sobe via systemd, o kernel cria o contexto daquele processo sob o PID 1. O systemd não serve apenas para reiniciar processos que caíram ou gerenciar dependências de boot; ele é uma interface direta para namespaces de mount, cgroups e restrições de chamadas de sistema.

Se o seu arquivo de unit se parece com isto:

```ini
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/app/current
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always

[Install]
WantedBy=multi-user.target
```

Você tem um processo rodando sem root, mas com visão panorâmica do servidor. Se um atacante explorar uma falha na sua stack e conseguir uma shell interativa com esse usuário, ele poderá:

1. Listar o conteúdo de `/home/` de outros usuários e tentar ler chaves públicas, históricos de bash ou arquivos residuais.
2. Ler e gravar livremente em `/tmp` e `/var/tmp`, usando essas pastas para baixar binários compilados, scanners de rede ou scripts de persistência.
3. Bisbilhotar `/etc` atrás de senhas em texto puro de serviços auxiliares.

Mudar isso exige virar a chave mental: não basta definir quem roda o processo, é preciso definir o que o processo enxerga do sistema operacional.

## Trancando a casa com ProtectHome e ProtectSystem

O primeiro passo para fechar a visibilidade desnecessária é dizer ao systemd que o processo não tem motivo para olhar para os diretórios residenciais de ninguém.

A diretiva `ProtectHome=true` monta `/home`, `/root` e `/run/user` como inacessíveis para o serviço. Eles simplesmente aparecem vazios ou com acesso negado via namespace de montagem privado, sem alterar as permissões reais no disco para os outros processos do sistema.

Se sua aplicação estiver hospedada dentro de `/home/deploy/app`, ativar `ProtectHome=true` vai quebrar o boot imediatamente. A alternativa aqui é mudar o deploy para caminhos padrão de servidor, como `/var/www` ou `/srv`, ou usar `ProtectHome=read-only` durante a transição. No longo prazo, manter código de produção dentro de `/home` de usuário é uma decisão de arquitetura ruim justamente por misturar ambiente interativo com serviço do sistema.

Em conjunto, aplicamos `ProtectSystem=strict`. Esta é a opção mais agressiva e necessária: ela monta todo o `/usr`, `/boot`, `/etc` e o restante do sistema de arquivos como somente leitura (`read-only`) para a unit.

```ini
ProtectSystem=strict
ProtectHome=true
```

Com apenas essas duas linhas, qualquer tentativa de alterar binários do sistema, sobrescrever arquivos em `/etc` ou mexer em bibliotecas compartilhadas falha imediatamente com erro de sistema de arquivos em modo somente leitura.

## Abrindo exceções cirúrgicas com ReadWritePaths

Com `ProtectSystem=strict`, o processo não consegue gravar em lugar nenhum. Uma aplicação web precisa gravar logs, precisa de uma pasta de uploads temporários, precisa de sockets UNIX e de pastas de cache.

Em vez de relaxar a segurança global, você declara explicitamente quais caminhos têm permissão de escrita através de `ReadWritePaths`:

```ini
ReadWritePaths=/var/www/app/shared/log /var/www/app/shared/tmp /var/www/app/shared/storage
```

Qualquer escrita fora dessa lista recebe um `EPERM` do kernel. O processo tenta gravar um arquivo em `/tmp`? Bloqueado. Tenta alterar um script no diretório raiz do projeto fora de `tmp` ou `storage`? Bloqueado.

Para isolar ainda mais o uso de arquivos temporários, ativamos `PrivateTmp=true`. Essa diretiva cria um namespace de `/tmp` e `/var/tmp` exclusivo para aquela unit. Se o Puma gravar um arquivo em `/tmp`, nenhum outro usuário ou serviço na máquina consegue enxergá-lo, e o Puma também não enxerga o `/tmp` real do host.

## Montando a unit completa

Juntando as diretivas de isolamento de sistema de arquivos com bloqueios de privilégios de kernel, a unit do serviço ganha outra densidade de segurança:

```ini
[Unit]
Description=Puma HTTP Server
After=network.target

[Service]
Type=simple
User=deploy
Group=deploy
WorkingDirectory=/var/www/app/current
ExecStart=/usr/local/bin/bundle exec puma -C config/puma.rb
Restart=always

## Isolamento de montagem e filesystem
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/www/app/shared/log /var/www/app/shared/tmp /var/www/app/shared/storage

## Restrições de privilégios e kernel
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictRealtime=true
RestrictSUIDSGID=true

[Install]
WantedBy=multi-user.target
```

A linha `NoNewPrivileges=true` impede que o processo ou qualquer subprocesso gerado por ele ganhe novos privilégios via binários com bit `setuid` (como `sudo` ou `pkexec`). Mesmo se houver uma vulnerabilidade local de elevação de privilégio instalada no sistema, a chamada `execve` não altera as credenciais do processo.

`RestrictSUIDSGID=true` garante que qualquer tentativa de criar arquivos com permissões SUID ou SGID seja ignorada, impedindo a criação de backdoors simples em diretórios graváveis.

## O custo operacional e onde o modelo costuma ranger

Seria desonesto afirmar que aplicar essas diretivas é um processo indolor. O isolamento rigoroso cobra seu preço na depuração diária.

O problema mais comum surge quando a aplicação precisa executar utilitários externos, como chamar o `ImageMagick`, `ffmpeg` ou ferramentas de backup que esperam gravar arquivos temporários em caminhos globais. Se a biblioteca de processamento tentar acessar `/tmp` sem que você tenha configurado caminhos explicitamente ou sem entender o `PrivateTmp`, o job falha com erros genéricos de permissão que não aparecem claramente no log da aplicação.

Outro ponto de atrito acontece durante o deploy. Se a sua ferramenta de deploy roda sob o mesmo usuário `deploy` via SSH, ela opera fora da unit do systemd e tem permissões normais do shell. Porém, se o processo em execução tentar recarregar arquivos ou reescrever assets durante a inicialização, a unit vai barrar qualquer operação fora dos `ReadWritePaths`.

Para auditar o impacto antes de aplicar em produção, o systemd oferece uma ferramenta excelente:

```bash
systemd-analyze security puma.service
```

Esse comando analisa cada flag de exposição da unit e gera uma pontuação de risco de 0 a 10, apontando exatamente quais proteções de exposição de rede, namespaces e filesystem continuam abertas.

Subir serviços em servidores próprios sem a camada de abstração pesada de containers não significa abrir mão de confinamento. O kernel Linux possui todas as primitivas necessárias para garantir que uma falha na camada de aplicação não comprometa a máquina inteira. Apenas certifique-se de que o isolamento seja imposto pelo init, e não pela frágil esperança de que um usuário comum não saberá onde procurar.
