Por que o Linux domina o mundo dos servidores
Entre em qualquer data center, abra qualquer console de nuvem ou docker exec em qualquer container e você estará, quase certamente, em um kernel Linux. Ele alimenta a maioria dos servidores web, todos os celulares Android, a maioria dos roteadores e a vasta maioria das instâncias na nuvem. Os motivos são antigos e sólidos: é gratuito, roda em quase qualquer hardware, é estável o suficiente para ficar online por anos e é totalmente automatizável via scripts.
Para um desenvolvedor, o ponto prático é mais específico. No momento em que você faz o deploy, deixa de ser alguém que edita arquivos em um editor e passa a ser alguém que digita comandos em um shell, geralmente via SSH, em uma máquina que você não consegue ver. As habilidades deste guia são as que tornam essa máquina legível em vez de assustadora.
Você não precisa se tornar um administrador de sistemas. Você precisa de fluência suficiente para navegar, ler logs, corrigir permissões, reiniciar um serviço e entender o que está escutando em uma porta.
WSL e macOS: você já sabe mais do que imagina
Se você usa macOS, você já está em um sistema Unix. O terminal, a estrutura do sistema de arquivos e a maioria dos comandos — ls, cd, grep, chmod, ssh — funcionam da mesma maneira. As diferenças estão principalmente no gerenciador de pacotes e em algumas flags de BSD versus GNU.
No Windows, o WSL 2 oferece um kernel Linux real dentro de uma máquina virtual leve. wsl --install instala o Ubuntu com um shell, e seus arquivos ficam acessíveis de ambos os lados. Este é o caminho recomendado, pois é o mesmo ambiente onde seu código será executado.
Mesmo que você nunca instale o Linux diretamente, o modelo mental se transfere completamente para containers, que são processos de userland do Linux usando um “disfarce” muito convincente.
O sistema de arquivos é uma única árvore
Ao contrário do Windows, onde as unidades são raízes separadas, o Linux possui uma única árvore enraizada em /. Cada disco, compartilhamento de rede e dispositivo é montado em algum lugar dentro dela. Alguns diretórios possuem significados convencionais, e conhecê-los significa que você sempre saberá onde procurar.
/ the root of everything
├── bin essential user binaries (ls, cp, bash)
├── etc system-wide configuration files
├── home per-user home directories
├── opt optional third-party software
├── proc kernel and process state, as files
├── root the root user's home
├── tmp temporary files, cleared on reboot
├── usr installed programs and libraries
│ ├── bin local binaries
│ └── lib shared libraries
└── var variable data: logs, caches, databases
├── log system and application logs
└── www web content on Debian systems
Os dois que você mais visitará são /etc para configurações e /var/log para logs. Quando algo estiver errado, esses são os primeiros lugares a serem verificados. Quando você instala um aplicativo por conta própria, /opt ou /usr/local o mantém isolado do gerenciador de pacotes.
Os caminhos são absolutos (começam com /, como /etc/hosts) ou relativos (resolvidos a partir do seu diretório atual). ~ é um atalho para o seu diretório home, e . é o diretório atual, sendo .. o diretório pai. Esses três símbolos aparecem em quase todos os comandos que você digitar.
Tudo é um arquivo
A ideia mais profunda do Unix é que quase tudo é representado como um arquivo. Um disco rígido é /dev/sda. Um terminal é /dev/pts/0. Informações de processos em execução ficam em /proc/<pid>/. Números aleatórios vêm de /dev/urandom. Um Unix domain socket também é um arquivo.
Essa uniformidade é o que torna o shell tão poderoso. Os mesmos cat, grep, less e operadores de redirecionamento funcionam em um arquivo de log, no mapa de memória de um processo e em um dispositivo, porque todos são apenas fluxos de bytes por trás de um caminho. Quando você entende isso, /proc e /sys deixam de parecer mágica e tornam-se inspecionáveis.
cat /proc/cpuinfo | grep 'model name' | head -1
ls -l /dev/null # the bit bucket
Navegação e os comandos de arquivo que você usará diariamente
O shell é um loop: ele lê uma linha, a expande, encontra o programa e o executa. Seis comandos cobrem a maior parte da navegação.
pwd # print working directory
ls -lah # list, long format, human sizes, hidden files
cd /var/log # change directory
cd - # back to the previous directory
cd ~ # home
tree -L 2 # a readable tree, two levels deep
Criar, copiar, mover e remover arquivos exige um vocabulário igualmente reduzido. mkdir -p cria pastas pai conforme necessário. cp -r copia diretórios recursivamente. mv renomeia arquivos dentro de um sistema de arquivos e os move entre eles. rm -rf deleta uma árvore inteira, e é por isso que ele merece respeito: não existe lixeira na linha de comando.
mkdir -p app/{src,test,docs}
cp -r app app.bak
mv notes.txt docs/notes.txt
rm -rf build/
Dois hábitos mantêm você seguro. Use ls ou find para confirmar o que um glob corresponde antes de executar o rm com ele, e prefira o mv para um diretório de backup em vez de deletar algo de que você não tenha certeza. O histórico do shell não é uma rede de segurança quando você pressiona Enter.
Lendo arquivos sem abrir um editor
Raramente você precisará de um editor completo para inspecionar algo em um servidor. Estas ferramentas imprimem apenas a parte que você precisa.
cat /etc/os-release # print the whole file
less /var/log/syslog # page through, / to search, q to quit
head -n 20 access.log # first 20 lines
tail -n 50 access.log # last 50 lines
tail -f app.log # follow a live log
wc -l access.log # count lines
tail -f é o comando mais útil durante um incidente: ele transmite novas linhas conforme elas são escritas. Pressione Ctrl+C para parar. less é mais rápido que um editor e nunca modifica o arquivo acidentalmente, o que é fundamental em produção.
Pesquisando com find e grep
find percorre o sistema de arquivos e faz a correspondência com metadados; grep pesquisa dentro do conteúdo dos arquivos. Juntos, eles respondem à maioria das perguntas do tipo “onde está isso?”.
find /etc -name '*.conf' # by name
find . -type d -name node_modules -prune -o -type f -name '*.ts' -print
find /var/log -type f -mtime -1 # modified in the last day
find . -type f -size +50M # larger than 50 MB
find . -type f -name '*.tmp' -delete # delete matches
grep -RIn 'TODO' src/ # recursive, line numbers, ignore binary
grep -c 'ERROR' app.log # count matches
grep -E 'timeout|refused' app.log # extended regex
grep -v '^#' config.ini # invert: drop comments
grep não retornar nada é um sucesso, não um erro, mas ele sai com o status 1. Isso costuma causar problemas em scripts sob set -e; adicione || true quando um resultado vazio for aceitável.
Permissões: rwx e a abreviação octal
Todo arquivo possui um dono, um grupo e três bits de permissão para cada: leitura (r), escrita (w) e execução (x). ls -l as exibe como uma string de dez caracteres.
-rwxr-xr-- 1 deploy www-data 512 Sep 16 10:00 deploy.sh
O sistema octal é a forma abreviada. Cada trio é a soma de 4 (leitura), 2 (escrita) e 1 (execução), portanto rwx é 7, r-x é 5 e r-- é 4. Isso faz com que 755 signifique rwxr-xr-x e 644 signifique rw-r--r--.
chmod 755 deploy.sh # scripts and directories
chmod 644 index.html # regular files
chmod 600 .env # secrets only the owner can read
chmod u+x,g-w,o-rwx script # symbolic form, easier to review
Diretórios precisam do bit de execução para serem acessados, e é por isso que remover x de um diretório oculta seu conteúdo, mesmo que a permissão de leitura ainda esteja ativa. Esta é uma fonte sutil de erros de “permission denied” quando a leitura parece estar correta.
Propriedade, grupos e umask
chown altera o proprietário e o grupo; chgrp altera apenas o grupo. Utilize o prefixo sudo porque apenas o root pode transferir a propriedade de arquivos.
sudo chown deploy:www-data /var/www/app
sudo chgrp -R www-data /var/www/app/uploads
Novos arquivos recebem suas permissões do umask, uma máscara subtraída do padrão. Um umask de 022 resulta em 644 para arquivos e 755 para diretórios; 027 resulta em 640 e 750. Configure-o no perfil do seu shell ou no arquivo de unidade de um serviço quando for necessário um padrão mais rigoroso.
umask # show current, e.g. 0022
umask 027 # stricter for this shell
sudo e o hábito do privilégio mínimo
O sudo executa um comando como outro usuário, root por padrão, após verificar o /etc/sudoers. Ele é deliberadamente restrito: conceda apenas os comandos específicos que uma pessoa ou serviço precisa, em vez de entregar um shell de root. Prefira o sudo -u www-data <cmd> a editar arquivos como root e deixá-los sob a propriedade do root.
sudo systemctl restart my-api
sudo -u postgres psql -c '\l'
sudo -l # what am I allowed to run?
Uma regra prática: o root detém a configuração do sistema, um usuário de serviço dedicado detém os arquivos da aplicação, e seu app nunca é executado como root. Se a invasão do seu app desse a um atacante acesso root no host, as permissões não estariam servindo para nada.
Usuários e grupos
Os usuários são identificados por um nome e um uid numérico; os grupos por um nome e um gid. O mapeamento fica em /etc/passwd e /etc/group, enquanto as senhas com hash ficam em /etc/shadow, legíveis apenas pelo root.
whoami # current user
id # uid, gid and groups
groups deploy # groups a user belongs to
sudo adduser appuser # create a user (Debian/Ubuntu)
sudo usermod -aG docker appuser # add to a group
sudo userdel -r appuser # remove the user and home
Contas de serviço geralmente não possuem senha nem shell de login. Isso é intencional: elas existem para serem proprietárias de arquivos e executar um único processo, não para que se faça login nelas.
Processos: ps, top e signals
Todo programa em execução é um processo com um pid, um proprietário e um pai. ps tira um snapshot; top e htop atualizam em tempo real.
ps aux | grep node # find a process
ps -eo pid,ppid,user,%cpu,%mem,cmd --sort=-%cpu | head
top -o %MEM # interactive, sort by memory
pgrep -af node # pids and full command lines
Processos se comunicam através de signals. SIGTERM (15) solicita que um processo seja encerrado e é o padrão para kill; SIGINT (2) é Ctrl+C; SIGHUP (1) tradicionalmente significa recarregar; SIGKILL (9) não pode ser interceptado e deve ser usado apenas como último recurso.
kill 1234 # SIGTERM, graceful
kill -HUP 1234 # reload config
kill -9 1234 # force, no cleanup
pkill -f 'node server.js'
Envie SIGTERM primeiro e dê ao processo alguns segundos para fechar conexões e limpar o estado. Escale para -9 apenas quando ele estiver genuinamente travado, pois isso ignora todos os caminhos de limpeza da sua aplicação.
Jobs em background, & e nohup
Adicionar & executa um comando em background; jobs, fg e bg o gerenciam a partir do shell atual. No entanto, um job em background ainda é encerrado quando você fecha o terminal, a menos que você o desvincule do sinal de hangup com nohup ou utilize um multiplexador de terminal.
long-task & # background in this shell
jobs -l # list background jobs
fg %1 # bring job 1 to the foreground
nohup ./worker.sh > worker.log 2>&1 &
disown # detach from the shell's job table
Para qualquer processo de longa duração, use tmux ou screen. Eles mantêm a sessão ativa mesmo após desconexões, o que é fundamental quando um deploy demora mais do que a sua conexão SSH.
tmux new -s deploy
# Ctrl+B, then D to detach
tmux attach -t deploy
serviços systemd com systemctl
Nas distribuições modernas, o systemd é o sistema de init que inicia os serviços no boot e os supervisiona. Você define um serviço em um arquivo de unidade (unit file) em /etc/systemd/system/.
# /etc/systemd/system/my-api.service
[Unit]
Description=My API
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/my-api
Environment=NODE_ENV=production
EnvironmentFile=/opt/my-api/.env
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
Após criar ou editar uma unidade, recarregue o gerenciador e habilite o serviço para que ele inicie no boot.
sudo systemctl daemon-reload
sudo systemctl enable --now my-api
systemctl status my-api --no-pager
sudo systemctl restart my-api
sudo systemctl reload my-api # if the service supports it
O Restart=on-failure é o que transforma um processo que crashou em um serviço com auto-recuperação (self-healing). O EnvironmentFile mantém segredos fora da unidade, que geralmente é legível por qualquer usuário. Use Type=notify apenas se o programa realmente suportar o protocolo de notificação; simple é o padrão seguro.
Logs com journalctl
O systemd captura o stdout e stderr de um serviço no journal. O journalctl faz a consulta a esses logs, e você o usará constantemente.
journalctl -u my-api # all logs for one unit
journalctl -u my-api -n 100 # last 100 lines
journalctl -u my-api -f # follow live
journalctl -u my-api --since today
journalctl -u my-api --since '10 min ago' -p err
journalctl -b -p warning # this boot, warnings and worse
journalctl --disk-usage
sudo journalctl --vacuum-time=7d # keep a week
A flag -p filtra por prioridade, de emerg até debug. Acompanhar uma unidade com -f enquanto você acessa um endpoint é a maneira mais rápida de entender a causa de um erro 500.
Variáveis de ambiente e PATH
Variáveis de ambiente são strings de chave-valor que um processo herda de seu processo pai. PATH é a mais importante: ela lista os diretórios que o shell pesquisa para encontrar o nome de um comando, em ordem.
echo "$PATH" | tr ':' '\n'
export NODE_ENV=production
export PATH="$HOME/.local/bin:$PATH"
printenv DATABASE_URL
Como um serviço não herda o seu shell interativo, defina as variáveis em seu arquivo de unidade ou em um EnvironmentFile. Quando uma variável funciona no seu terminal, mas não sob o systemd, quase sempre o motivo é este. Para persistir suas próprias configurações, adicione as linhas export ao ~/.bashrc ou ~/.profile.
Gerenciadores de pacotes: apt, dnf, apk
Cada família de distribuição possui seu próprio gerenciador de pacotes, mas os verbos são semelhantes: atualizar o índice, instalar, atualizar (upgrade), remover e pesquisar.
# Debian / Ubuntu
sudo apt update && sudo apt upgrade -y
sudo apt install nginx
sudo apt remove --purge nginx
# Fedora / RHEL
sudo dnf install nginx
sudo dnf upgrade --refresh
# Alpine (common in containers)
sudo apk add nginx
sudo apk upgrade
Instale apenas de repositórios oficiais sempre que possível; eles são assinados e corrigidos. Utilize um PPA ou repositório de terceiros de forma deliberada, e fixe as versões em imagens de produção para que um rebuild não altere silenciosamente o que está em execução.
Pipes, redirecionamento e scripts simples
Um pipe envia a saída de um programa para a entrada de outro. O redirecionamento envia essa saída para um arquivo ou a lê de um. Juntos, eles transformam pequenas ferramentas em pipelines.
ls -l | wc -l # count files
grep ' 500 ' access.log | wc -l # count 500s
cmd > out.txt # stdout to file, overwrite
cmd >> out.txt # append
cmd 2> err.txt # stderr to file
cmd > all.txt 2>&1 # both streams together
cmd < input.txt # read stdin from a file
Todo processo começa com três descritores de arquivo: stdin (0), stdout (1) e stderr (2). 2>&1 significa “envie o stderr para onde o stdout está indo”, e a ordem é importante — ele deve vir após o redirecionamento do stdout.
Envolva uma sequência em um script e você terá automação. Duas linhas no topo tornam os scripts muito mais seguros:
#!/usr/bin/env bash
set -euo pipefail
-e encerra a execução no primeiro comando que falhar, -u gera erro em variáveis não definidas e -o pipefail faz com que um pipeline falhe se qualquer etapa falhar. Juntos, eles transformam falhas parciais silenciosas em falhas explícitas, que é exatamente o que você deseja em um script de deploy.
SSH e chaves
O SSH é a forma como você acessa uma máquina remota. Logins por senha funcionam, mas chaves são mais seguras e permitem automação via scripts. Você mantém a chave privada em segredo e coloca a chave pública correspondente no servidor.
ssh-keygen -t ed25519 -C "you@laptop" # creates ~/.ssh/id_ed25519(.pub)
ssh-copy-id [email protected] # install the public key
ssh [email protected] # log in
Um alias de host no ~/.ssh/config evita digitação repetitiva e fixa opções úteis:
Host prod
HostName 203.0.113.10
User deploy
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
Depois, o ssh prod realiza a conexão. Assim que você confiar nas chaves, desative a autenticação por senha no /etc/ssh/sshd_config (PasswordAuthentication no) e reinicie o sshd. O scp e o rsync reutilizam as mesmas chaves para copiar arquivos; o rsync -avz --delete é a maneira usual de sincronizar um diretório.
rsync -avz --exclude node_modules ./app/ prod:/opt/app/
ssh prod 'sudo systemctl restart my-api'
Disco e memória: df, du, free
Servidores raramente falham por causa da CPU; eles falham porque o disco enche ou a memória acaba. Estes comandos respondem a “quanto resta?” e “o que está consumindo?”.
df -h # free space per filesystem
df -i # inode usage — a disk can be full of inodes
du -sh /var/log/* # size per entry
du -h --max-depth=1 / | sort -h
free -h # memory and swap
ls -lhS /var/log | head # biggest log files
Quando /var está cheio, a causa é quase sempre logs, cache de pacotes ou um banco de dados descontrolado. Faça a limpeza de forma deliberada — journalctl --vacuum-time, apt clean, rotação de logs — em vez de deletar arquivos que a aplicação espera encontrar.
Networking: ss, curl, dig
ss mostra sockets e portas em escuta, substituindo o antigo netstat. Ele responde a “tem algo escutando e quem está conectado?”.
ss -tulpn # TCP, UDP, listening, process names
curl -I https://example.com # response headers only
curl -sS localhost:3000/health
curl -X POST -H 'content-type: application/json' \
-d '{"name":"ada"}' localhost:3000/users
dig example.com +short # DNS resolution
dig @1.1.1.1 example.com MX
curl -I é a maneira mais rápida de confirmar se um servidor está respondendo e qual status ele retorna. Se ss não mostrar nada em uma porta, mas o app disser que iniciou, ele pode estar vinculado ao 127.0.0.1 em vez do 0.0.0.0, o que o torna invisível de fora do host.
Agendamento com cron
O cron executa comandos em um cronograma. Cada usuário possui um crontab com cinco campos de tempo seguidos pelo comando.
# ┌ min (0-59)
# │ ┌ hour (0-23)
# │ │ ┌ day of month (1-31)
# │ │ │ ┌ month (1-12)
# │ │ │ │ ┌ day of week (0-6, Sun=0)
# * * * * * command
crontab -e # edit your crontab
crontab -l # list it
0 3 * * * /opt/app/backup.sh >> /var/log/backup.log 2>&1
*/5 * * * * curl -fsS localhost:3000/health > /dev/null
0 7 * * 1 /opt/app/weekly-report.sh
O ambiente do cron é minimalista: ele possui um PATH diferente, não tem perfil de shell nem variáveis interativas. Use caminhos absolutos, redirecione a saída para um log para que as falhas fiquem visíveis e lembre-se de que o cron não tenta executar novamente uma tarefa que falhou. Para qualquer coisa mais complexa, agende um timer do systemd ou coloque um job em uma fila.
Melhores práticas
- Use caminhos absolutos em scripts e arquivos de unidade; nunca dependa do ambiente interativo.
- Coloque
set -euo pipefailno topo de todo shell script que não seja trivial. - Conceda o privilégio mínimo: usuários de serviço dedicados,
sudopara comandos específicos e nenhum app rodando como root. - Prefira
SIGTERMe permita que os serviços sejam encerrados graciosamente antes de recorrer aokill -9. - Configure
Restart=on-failuree leiajournalctl -u <unit>ao realizar o debugging. - Mantenha a configuração no controle de versão e altere apenas uma coisa por vez em um host em produção.
- Rotacione os logs e monitore
df -hedf -i; os discos enchem silenciosamente. - Use chaves SSH, não senhas, e mantenha as chaves privadas fora dos servidores.
- Aprenda
find,grep,awke pipes — eles substituem diversas ferramentas pontuais. - Teste uma configuração antes de recarregar o serviço que a utiliza.
Erros comuns
- Executar aplicações como root, transformando qualquer bug em um comprometimento total do sistema.
- Usar
chmod -R 777para “corrigir” permissões, removendo todas as proteções entre usuários. - Deletar arquivos usando um wildcard sem revisão e sem ter backup.
- Enviar
kill -9primeiro e corromper o estado que um graceful shutdown teria persistido. - Definir variáveis de ambiente no seu shell e esperar que o systemd as reconheça.
- Assumir que um serviço está fora do ar quando ele está vinculado apenas ao
127.0.0.1. - Esquecer o
2>&1em entradas do cron e perder o erro que explica a falha de um job. - Editar configurações diretamente no servidor sem controle de versão e sem forma de reverter.
- Ignorar a exaustão de inodes porque o
df -hainda mostra espaço livre. - Deixar a autenticação por senha habilitada em uma porta SSH exposta à internet.
Próximos passos
O Linux é a base de tudo o que você implanta. Agora que você já consegue navegar em um servidor, ler logs e reiniciar serviços, o guia de Nginx apresenta o servidor web que você configurará à frente da sua aplicação, e o de Docker & Deployment transforma esses primitivos em imagens isoladas e repetíveis. Para automatizar esses mesmos comandos a cada push, leia sobre CI/CD e, para entender o processo que seu serviço realmente executa, revise os Node.js Basics.