🍺 Buy me a beer
🐧

Linux Admin per Svogliati

Filesystem, utenti, processi, dischi, systemd, log, SSH, performance e tutto quello che serve per non farsi fregare dal server alle 3 di notte.
Perché "ho letto la man page" non l'ha mai detto nessuno in produzione.

"È già tutto scritto nella documentazione" — sì, e ci vogliono 4 ore per trovare il pezzo che serve.

01 / filesystem

Filesystem Hierarchy

Dove vive cosa, e perché non mettere tutto in /tmp

🏠 L'analogia del condominio

Linux ha un unico albero di directory che parte da / (root). Non esistono C:\ e D:\: tutto è montato da qualche parte nell'albero. I dischi aggiuntivi vengono "montati" come cartelle — /mnt/dati, /home, /var possono stare su dischi fisici diversi, ma li vedi tutti nello stesso posto.

/ # la radice. Tutto parte da qui. ├── etc/ # configurazioni di sistema (NON dati, NON binari) │ ├── passwd # lista utenti (no password, quelle sono in shadow) │ ├── shadow # hash password — leggibile solo da root │ ├── fstab # montaggi automatici al boot │ ├── hosts # DNS locale (override di tutto) │ ├── crontab # cron di sistema │ └── systemd/ # unit file di sistema ├── var/ # dati variabili (log, spool, cache — CRESCONO nel tempo) │ ├── log/ # log testuali (syslog, auth.log, nginx/, ...) │ ├── lib/ # dati persistenti delle applicazioni (PostgreSQL, Docker...) │ └── spool/ # code (mail, cron, stampa) ├── home/ # home degli utenti (/home/marco, /home/sara) ├── root/ # home di root (non sta in /home — privilegiato) ├── usr/ # binari e librerie installati (read-only in teoria) │ ├── bin/ # comandi utente (ls, grep, curl...) │ ├── sbin/ # comandi amministrativi (ifconfig, fdisk...) │ └── local/ # roba installata manualmente (non dal package manager) ├── bin/ # symlink a /usr/bin (nei sistemi moderni) ├── proc/ # filesystem virtuale — informazioni sui processi in tempo reale ├── sys/ # filesystem virtuale — interfaccia al kernel (hardware, driver) ├── dev/ # device file (sda, tty, null, random, urandom...) ├── tmp/ # temporanei — svuotato al reboot. Non ci mettere roba importante. ├── run/ # runtime (PID file, socket — come /tmp ma per i servizi) ├── opt/ # software opzionale di terze parti (Chrome, Slack, roba commerciale) ├── boot/ # kernel e bootloader (vmlinuz, initrd, grub) ├── lib/ # librerie condivise (.so) e moduli kernel └── mnt/ srv/ # montaggi manuali e dati servizi (convenzione, non obbligo)
⚠️ Disco pieno su /var
Il problema più classico del Linux admin. I log crescono, il database scrive, Docker accumula layer. Controlla sempre df -h e du -sh /var/* prima di essere svegliato alle 3 di notte da un alert. Se il disco è pieno non riesci nemmeno a fare il login SSH in certi casi.

🔍 Comandi fondamentali per orientarsi

# quanto spazio hai
df -h                         # tutti i filesystem montati, human-readable
df -h /                       # solo il root

# cosa occupa spazio
du -sh /var/log/*             # dimensione di ogni file/dir in /var/log
du -sh /* 2>/dev/null         # top-level, ignora errori permessi
du -sh /var/* | sort -h      # ordina per dimensione

# navigazione rapida
ls -lah /etc/                 # lista dettagliata con dimensioni human-readable
file /dev/sda                 # che tipo di file è?
stat /etc/passwd              # inode, permessi, timestamp completi
readlink -f /bin/python3      # risolvi symlink fino alla destinazione reale
lsof /var/log/syslog          # chi ha aperto questo file?
fuser /var/log/syslog         # quali processi usano questo file

🗂️ /proc e /sys — la finestra sul kernel

Non sono directory reali — sono filesystem virtuali generati al volo dal kernel. Ogni numero in /proc è un PID.

cat /proc/cpuinfo             # info CPU
cat /proc/meminfo             # info RAM dettagliate
cat /proc/loadavg             # load average corrente
cat /proc/1/cmdline           # command line del PID 1 (systemd/init)
cat /proc/1/environ           # variabili d'ambiente del PID 1
ls -la /proc/self/fd/         # file descriptor della shell corrente

cat /sys/class/net/eth0/speed # velocità interfaccia di rete
cat /sys/block/sda/size       # dimensione disco in settori da 512 byte
02 / utenti

Utenti, Gruppi & Permessi

Tutto gira con un UID. Anche root è solo UID 0.

👤 Utenti

# creare un utente con home e shell
useradd -m -s /bin/bash mario
passwd mario

# modificare utente esistente
usermod -aG sudo mario    # aggiungi a gruppo sudo
usermod -s /bin/bash mario # cambia shell
usermod -l newname mario  # rinomina utente

# info utente
id mario          # uid, gid, gruppi
getent passwd mario  # riga da /etc/passwd
groups mario      # lista gruppi
last mario        # ultimi login
w                 # chi è loggato adesso

# bloccare/sbloccare account
usermod -L mario  # lock
usermod -U mario  # unlock
userdel -r mario  # elimina utente + home

👥 Gruppi

# gestione gruppi
groupadd developers
groupdel developers
gpasswd -a mario developers  # aggiungi
gpasswd -d mario developers  # rimuovi

# file chiave
/etc/passwd   # utenti: nome:x:uid:gid:info:home:shell
/etc/shadow   # hash password (solo root)
/etc/group    # gruppi: nome:x:gid:membri
/etc/sudoers  # chi può fare sudo (usa visudo!)

# sudo best practice
visudo   # modifica /etc/sudoers con validazione
# mario ALL=(ALL:ALL) ALL
# mario ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx
⚠️ Non modificare mai /etc/sudoers direttamente con nano. Un errore di sintassi e perdi l'accesso sudo. Usa sempre visudo — fa la validazione.

🔐 Permessi: rwx e il sistema ottale

Ogni file ha 3 set di permessi: owner, group, others. Si leggono da sinistra a destra.

-rwxr-xr--
- rwx r-x r--
file | owner: rwx (7) | group: r-x (5) | others: r-- (4) → chmod 754
drwxr-xr-x
d rwx r-x r-x
directory | owner: rwx | group: r-x | others: r-x → chmod 755
# chmod numerica (più veloce)
chmod 755 script.sh       # rwxr-xr-x (standard per script/dir)
chmod 644 config.conf    # rw-r--r-- (standard per file config)
chmod 600 .env           # rw------- (solo owner — file segreti)
chmod 700 /root          # rwx------ (solo root)

# chmod simbolica (più leggibile)
chmod u+x script.sh      # aggiungi execute all'owner
chmod g-w file.txt       # rimuovi write al group
chmod o=r file.txt       # others: solo read
chmod a+x /usr/local/bin/tool  # tutti: aggiungi execute
chmod -R 755 /var/www/html     # ricorsivo

# chown
chown mario file.txt          # cambia owner
chown mario:developers file.txt # owner e group
chown -R www-data:www-data /var/www  # ricorsivo

# umask — maschera default per nuovi file
umask           # mostra umask corrente (tipico: 022)
# umask 022 → file creati con 644, dir con 755
# umask 027 → file con 640, dir con 750 (più restrittivo)

Bit speciali: SUID, SGID, Sticky

Quelli che tutti dimenticano finché non servono.

BitSu fileSu directoryEsempio
SUID (4xxx)Esegue come owner del file (anche se sei un altro utente)Ignorato/usr/bin/passwd — gira come root per cambiare /etc/shadow
SGID (2xxx)Esegue come group del fileNuovi file ereditano il group della dirchmod 2755 /shared — tutti i file in /shared appartengono al gruppo della dir
Sticky (1xxx)ObsoletoSolo il proprietario può cancellare i propri file/tmp — tutti scrivono, nessuno cancella i file degli altri
chmod u+s /usr/bin/myprog    # SUID
chmod g+s /shared            # SGID su directory
chmod +t /tmp                # sticky bit
# ls -la mostrerà: -rwsr-xr-x (SUID), drwxrwsr-x (SGID), drwxrwxrwt (sticky)

# trova tutti i SUID nel sistema — utile per audit sicurezza
find / -perm -4000 -type f 2>/dev/null
03 / processi

Processi & Gestione

Tutto è un processo. Anche quello che sta bloccando il tuo server adesso.

📋 Visualizzare processi

$ ps aux | head
PID%CPU%MEMSTATCOMMAND
10.00.1Ss/sbin/init (systemd)
8920.32.4R+nginx: worker process
124712.118.7Rpython3 app.py
28910.00.0Z[defunct]
ps aux                        # tutti i processi
ps aux | grep nginx           # filtra per nome
ps -ef --forest               # albero gerarchico padre-figlio
pgrep nginx                   # solo i PID di nginx
pgrep -l nginx                # PID + nome
pidof nginx                   # PID(s) di nginx
top                           # interattivo, tasto 1 per CPU per core
htop                          # come top ma bello (installa: apt install htop)
ℹ️ Stati processi: S = sleeping (in attesa), R = running, Z = zombie (terminato ma il padre non ha fatto wait()), D = uninterruptible sleep (spesso I/O bloccato — disco o rete), T = stopped. I processi Z non consumano risorse ma occupano un PID — se ne hai tanti, il problema è nel processo padre.

🎯 Mandare segnali ai processi

# i segnali più usati
kill -15 1247      # SIGTERM — "per favore chiudi" (graceful)
kill -9 1247       # SIGKILL — "muori adesso" (non interceptabile)
kill -1 1247       # SIGHUP — "ricarica config" (per daemon come nginx)
kill -2 1247       # SIGINT — equivalente Ctrl+C
kill -19 1247      # SIGSTOP — sospendi (come Ctrl+Z)
kill -18 1247      # SIGCONT — riprendi

# per nome
pkill nginx        # SIGTERM a tutti i processi "nginx"
pkill -9 python3   # SIGKILL a tutti i python3
killall nginx      # alternativa a pkill

# usa sempre -15 prima di -9
# -9 non dà al processo tempo di pulire — file aperti, lock, connessioni

🎛️ Job control, nice e priorità

# background / foreground
comando &          # lancia in background
Ctrl+Z             # sospendi processo corrente
bg                 # riprendi in background
fg                 # riporta in foreground
jobs               # lista job correnti della shell
nohup comando &    # sopravvive alla chiusura della shell (SIGHUP ignorato)
disown %1          # sgancia job 1 dalla shell (non riceve più SIGHUP)

# priorità (nice: -20 = max priorità, 19 = min)
nice -n 10 comando          # lancia con nice 10 (meno priorità)
renice -n 5 -p 1247         # cambia nice di processo esistente
renice -n -5 -p 1247        # aumenta priorità (solo root può scendere sotto 0)
04 / systemd

systemd

Il gestore di tutto. O quasi tutto. Ok, di tutto.

📌 systemd in una riga

systemd è il PID 1 — il primo processo che parte dopo il kernel. Gestisce tutto il ciclo di vita dei servizi, il boot, il logging (journald), i mount, i socket, i timer. Se un processo non è gestito da systemd su un server moderno, probabilmente è un errore.

⚙️ systemctl — i comandi essenziali

# stato e controllo servizi
systemctl status nginx         # stato dettagliato + ultimi log
systemctl start nginx
systemctl stop nginx
systemctl restart nginx        # stop + start (interrompe connessioni)
systemctl reload nginx         # ricarica config senza interrompere (se supportato)
systemctl enable nginx         # parte automaticamente al boot
systemctl disable nginx        # non parte al boot
systemctl enable --now nginx   # enable + start in una sola riga
systemctl mask nginx           # impedisce completamente l'avvio (anche manuale)

# overview sistema
systemctl list-units --type=service      # tutti i servizi attivi
systemctl list-units --failed            # servizi falliti
systemctl list-unit-files --type=service # tutti + stato enable/disable
systemctl is-active nginx               # active / inactive / failed
systemctl is-enabled nginx              # enabled / disabled

# boot e target
systemctl get-default          # target di default (graphical.target / multi-user.target)
systemctl set-default multi-user.target  # server senza GUI
systemctl reboot
systemctl poweroff
systemctl rescue               # modalità recovery

📄 Scrivere un unit file

Per far girare un'applicazione custom come servizio. I file vanno in /etc/systemd/system/.

# /etc/systemd/system/myapp.service [Unit] Description=La mia app Python After=network.target postgresql.service # parte dopo questi Requires=postgresql.service # dipendenza obbligatoria [Service] Type=simple # simple, forking, oneshot, notify, idle User=appuser # NON girare come root se puoi evitarlo Group=appuser WorkingDirectory=/opt/myapp EnvironmentFile=/etc/myapp/env # variabili d'ambiente da file ExecStart=/opt/myapp/venv/bin/python3 app.py ExecReload=/bin/kill -HUP $MAINPID Restart=on-failure # always, on-failure, no, on-abnormal RestartSec=5s StandardOutput=journal StandardError=journal # hardening (opzionale ma consigliato) NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ReadWritePaths=/var/lib/myapp [Install] WantedBy=multi-user.target
# dopo aver creato/modificato il file
systemctl daemon-reload           # rileggi tutti i file unit
systemctl enable --now myapp
systemctl status myapp

📰 journalctl — i log di systemd

journalctl -u nginx                   # log del servizio nginx
journalctl -u nginx -f                # segui in tempo reale
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx --since "2024-01-15 10:00" --until "2024-01-15 11:00"
journalctl -u nginx -n 50             # ultime 50 righe
journalctl -p err                     # solo errori (err, warning, info, debug)
journalctl -b                         # log dal boot corrente
journalctl -b -1                      # log dal boot precedente
journalctl --disk-usage               # quanto spazio occupano i log
journalctl --vacuum-time=7d           # elimina log più vecchi di 7 giorni
05 / dischi

Dischi, Partizioni & LVM

Perché "disco pieno" è l'errore più evitabile e il più comune

💾 Nomenclatura e panoramica

# vedere dischi e partizioni
lsblk                   # albero dischi/partizioni/mount
lsblk -f               # + filesystem e UUID
fdisk -l               # dettagli partizioni (richiede root)
blkid                  # UUID e tipo filesystem di ogni device
parted -l              # alternativa moderna a fdisk

# nomi tipici
# /dev/sda        → primo disco SCSI/SATA/USB
# /dev/sda1       → prima partizione
# /dev/nvme0n1    → primo SSD NVMe
# /dev/nvme0n1p1  → prima partizione NVMe
# /dev/vda        → disco virtuale (VM KVM/QEMU)

🔧 Creare partizioni e filesystem

# partizionare (interattivo)
fdisk /dev/sdb        # MBR (legacy, <2TB)
gdisk /dev/sdb        # GPT (moderno, >2TB, UEFI)
parted /dev/sdb       # alternativa non-interattiva possibile

# creare filesystem
mkfs.ext4 /dev/sdb1   # ext4 (standard Linux)
mkfs.xfs /dev/sdb1    # XFS (ottimo per grandi file, usato da RHEL)
mkfs.vfat /dev/sdb1   # FAT32 (USB compatibilità universale)
mkswap /dev/sdb2      # partizione swap

# montare
mount /dev/sdb1 /mnt/dati
umount /mnt/dati
mount -o remount,ro /mnt/dati  # rimonta in sola lettura

# montaggio permanente in /etc/fstab
# UUID=abc123 /mnt/dati ext4 defaults,nofail 0 2
# usa UUID (non /dev/sdb1) — i device name cambiano al riavvio
mount -a               # monta tutto da fstab (testa prima di riavviare)
🚨 L'errore classico di fstab
Scrivi un fstab sbagliato, rebooti, e il server non si avvia più. Sempre testare con mount -a prima del reboot. L'opzione nofail fa sì che il boot continui anche se il mount fallisce.

🏗️ LVM — Logical Volume Manager

LVM aggiunge un layer di astrazione tra dischi fisici e filesystem: puoi ridimensionare, aggiungere dischi, fare snapshot senza downtime.

/dev/vgdata/lv_home → ext4 → /home
/dev/vgdata/lv_data → xfs → /var/lib
Logical Volumes (LV) — quello che vedi tu
Volume Group: vgdata (500 GB totali)
Volume Group (VG) — pool di spazio
/dev/sdb (200 GB)
/dev/sdc (300 GB)
Physical Volumes (PV) — dischi fisici o partizioni
# creare LVM da zero
pvcreate /dev/sdb /dev/sdc             # inizializza PV
vgcreate vgdata /dev/sdb /dev/sdc      # crea VG
lvcreate -L 100G -n lv_home vgdata     # LV da 100GB
lvcreate -l 100%FREE -n lv_data vgdata # LV con tutto lo spazio rimasto
mkfs.ext4 /dev/vgdata/lv_home
mount /dev/vgdata/lv_home /home

# espandere un LV (senza downtime su ext4/xfs)
lvextend -L +50G /dev/vgdata/lv_home   # aggiungi 50GB
resize2fs /dev/vgdata/lv_home          # ridimensiona filesystem ext4
xfs_growfs /home                       # ridimensiona filesystem xfs

# stato
pvs   # physical volumes
vgs   # volume groups
lvs   # logical volumes

# snapshot LVM (backup istantaneo)
lvcreate -s -L 10G -n lv_home_snap /dev/vgdata/lv_home
06 / pacchetti

Package Management

apt, dnf, e perché non fare curl | bash in produzione

📦 Debian / Ubuntu (apt)

apt update              # aggiorna lista pacchetti
apt upgrade             # aggiorna pacchetti installati
apt full-upgrade        # upgrade + rimuovi obsoleti
apt install nginx
apt install nginx=1.24.0-1 # versione specifica
apt remove nginx        # rimuovi (lascia config)
apt purge nginx         # rimuovi + config
apt autoremove          # rimuovi dipendenze orfane
apt search nginx
apt show nginx          # dettagli pacchetto
apt list --installed
dpkg -l | grep nginx   # basso livello
dpkg -L nginx           # file installati dal pacchetto
dpkg -S /usr/sbin/nginx # quale pacchetto ha installato questo file

📦 RHEL / CentOS / Fedora (dnf)

dnf check-update
dnf update
dnf install nginx
dnf remove nginx
dnf search nginx
dnf info nginx
dnf list installed
dnf provides /usr/sbin/nginx # quale pacchetto installa questo file
rpm -ql nginx           # file installati (basso livello)
rpm -qa                 # tutti i pacchetti installati

# repository
dnf repolist
dnf config-manager --add-repo URL

🔐 Repository e chiavi GPG

# aggiungere un repo di terze parti (es. Docker su Ubuntu)
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list
apt update && apt install docker-ce

# i file dei repo stanno in
/etc/apt/sources.list          # file principale
/etc/apt/sources.list.d/*.list # file aggiuntivi (uno per repo)
⚠️ curl | bash è comodo, è pericoloso
Stai eseguendo codice sconosciuto con i privilegi di root. Almeno scarica lo script, leggilo, e poi eseguilo separatamente. In produzione preferisci sempre il package manager ufficiale o file .deb/.rpm firmati.
07 / log

Log & Monitoraggio

I log ci sono sempre. Il problema è trovarli prima che il server esploda.

📁 Dove stanno i log

FileContiene
/var/log/syslog o messagesLog di sistema generale (kernel, daemon, servizi)
/var/log/auth.log o secureLogin, sudo, SSH, autenticazione
/var/log/kern.logMessaggi kernel (driver, hardware, OOM killer)
/var/log/dpkg.logInstallazioni/rimozioni pacchetti
/var/log/nginx/access.logOgni richiesta HTTP ricevuta da nginx
/var/log/nginx/error.logErrori nginx (502, config sbagliata, permessi)
/var/log/postgresql/Log PostgreSQL (query lente, errori)
journalctlTutto quello gestito da systemd (binary journal)

🔍 Leggere e cercare nei log

# seguire in tempo reale
tail -f /var/log/syslog
tail -f /var/log/nginx/error.log
tail -n 100 /var/log/auth.log

# cercare pattern
grep "Failed password" /var/log/auth.log
grep -i error /var/log/syslog | tail -50
grep "Jan 15" /var/log/syslog
grep -v "healthcheck" /var/log/nginx/access.log  # escludi pattern

# chi sta tentando di entrare nel tuo server
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head

# ultime connessioni SSH riuscite
grep "Accepted" /var/log/auth.log
last             # tutti i login (ssh + console)
lastb            # login falliti
lastlog          # ultimo login per ogni utente

🔄 logrotate — evitare il disco pieno di log

# configurazione in /etc/logrotate.d/myapp
/var/log/myapp/*.log {
    daily          # ruota ogni giorno
    rotate 14      # mantieni 14 file
    compress       # comprimi con gzip
    delaycompress  # comprimi il precedente, non il corrente
    missingok      # non fare errore se il file non esiste
    notifempty     # non ruotare se vuoto
    create 640 www-data adm   # crea nuovo file con questi permessi
    postrotate
        systemctl reload myapp  # riapri il file di log
    endscript
}

# testare logrotate
logrotate -d /etc/logrotate.d/myapp    # dry run
logrotate -f /etc/logrotate.d/myapp    # forza rotazione ora
08 / performance

Performance & Risorse

Capire perché è lento prima di dire "serve più RAM"

🖥️ CPU e Load Average

uptime
 14:23:01 up 42 days, load average: 0.52, 1.23, 2.45
#                                    1min  5min  15min
ℹ️ Load average: cosa significa
Indica quanti processi sono in attesa di CPU (o disco). Su un server con 4 core: load 4.0 = ogni core è impegnato al 100% (OK), load 8.0 = c'è il doppio del lavoro rispetto alle risorse (problema). Su single core: load > 1 è già preoccupante. Il valore a 15 minuti è il più indicativo per capire il trend.
nproc                     # numero di CPU/core disponibili
lscpu                     # dettagli architettura CPU
mpstat 1                  # utilizzo CPU per core ogni secondo
top                       # premi 1 per vedere ogni core, P per ordinare per CPU
pidstat -u 1              # CPU per processo ogni secondo

🧠 Memoria RAM

free -h
              total  used  free  shared  buff/cache  available
Mem:           15Gi  5.2Gi 2.1Gi  420Mi      8.1Gi      9.6Gi
Swap:           2Gi  100Mi 1.9Gi

# "available" è quello che conta davvero — include buff/cache liberabile
# "free" basso ma "available" alto = normale, Linux usa la RAM per cache

vmstat 1           # virtual memory stats ogni secondo
cat /proc/meminfo  # dettagli completi

# top memory consumers
ps aux --sort=-%mem | head -10
smem -r -k | head   # se installato, più accurato di ps
🚨 OOM Killer — Out Of Memory Killer
Quando la RAM finisce, il kernel uccide processi per sopravvivere. Trovi la vittima nei log: grep -i "oom\|killed process" /var/log/kern.log o journalctl -k | grep -i oom. Non è un crash del sistema — è il kernel che ha scelto quale processo sacrificare. Soluzioni: aggiungere swap, aumentare RAM, o limitare i processi con cgroups.

💿 I/O Disco

iostat -x 1           # I/O per disco ogni secondo
# %util vicino a 100% = disco saturo
# await alto = latenza alta (disco lento o sovraccarico)

iotop                 # chi sta scrivendo/leggendo (apt install iotop)
iotop -o              # mostra solo processi con I/O attivo

# velocità disco
dd if=/dev/zero of=/tmp/test bs=1G count=1 oflag=direct  # write speed
dd if=/tmp/test of=/dev/null bs=1G count=1 iflag=direct   # read speed

# statistiche I/O per processo
pidstat -d 1

🌐 Rete

ss -tuln              # porte in ascolto (t=TCP, u=UDP, l=listening, n=no DNS)
ss -tp                # connessioni TCP con PID
ss -s                 # statistiche sommarie
netstat -i            # statistiche per interfaccia (bytes in/out, errori)
ip -s link            # stesso, alternativa moderna
sar -n DEV 1          # traffico per interfaccia ogni secondo
nload                 # grafico banda in tempo reale (apt install nload)
09 / ssh

SSH, SCP, rsync

Il tuo coltellino svizzero per tutto quello che è remoto

🔑 Chiavi SSH — l'unico modo corretto

# genera coppia di chiavi (fai questa sul tuo PC locale)
ssh-keygen -t ed25519 -C "mario@laptop"    # moderno, consigliato
ssh-keygen -t rsa -b 4096 -C "mario@laptop" # se il server è vecchio
# chiave privata: ~/.ssh/id_ed25519  (NON condividere MAI)
# chiave pubblica: ~/.ssh/id_ed25519.pub  (questa metti sul server)

# copiare la chiave pubblica sul server
ssh-copy-id -i ~/.ssh/id_ed25519.pub mario@server.esempio.it
# oppure manualmente: cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys

# ~/.ssh/config — risparmia digitazione
cat ~/.ssh/config
Host produzione
    HostName server.esempio.it
    User mario
    IdentityFile ~/.ssh/id_ed25519
    Port 22

Host jumphost
    HostName bastion.esempio.it
    User mario

Host prod-interno
    HostName 192.168.1.50
    User root
    ProxyJump jumphost

# ora puoi fare semplicemente
ssh produzione
ssh prod-interno   # passa automaticamente per jumphost

🔒 Hardening sshd

# /etc/ssh/sshd_config — modifiche consigliate
Port 2222                        # cambia porta (riduce rumore nei log)
PermitRootLogin no              # root non si logga via SSH
PasswordAuthentication no       # solo chiavi, niente password
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3
ClientAliveInterval 300         # keep-alive ogni 5 minuti
ClientAliveCountMax 2
AllowUsers mario deploy         # whitelist utenti (opzionale)
X11Forwarding no                # disabilita X forwarding se non serve

sshd -t             # valida la configurazione PRIMA di riavviare
systemctl reload ssh # ricarica (non perdere la sessione corrente!)
🚨 Prima di impostare PasswordAuthentication no, verifica che la tua chiave pubblica sia in ~/.ssh/authorized_keys e che funzioni. Altrimenti ti chiudi fuori dal server. Mantieni sempre una sessione SSH aperta mentre fai modifiche a sshd_config.

📁 Copiare file: scp e rsync

# scp — copia semplice
scp file.txt mario@server:/tmp/           # locale → remoto
scp mario@server:/var/log/syslog .        # remoto → locale
scp -r cartella/ mario@server:/opt/       # ricorsivo
scp -P 2222 file.txt mario@server:/tmp/   # porta personalizzata

# rsync — sincronizzazione intelligente (trasferisce solo differenze)
rsync -avz /var/www/ mario@server:/var/www/    # sync directory
rsync -avz --delete /backup/ mario@server:/backup/  # --delete rimuove file non presenti in origine
rsync -avz --exclude '*.log' /opt/app/ mario@server:/opt/app/
rsync -avz -e "ssh -p 2222" /dir/ mario@server:/dir/  # porta custom
rsync --dry-run -avz /dir/ mario@server:/dir/  # simula, non trasferisce

🖥️ tmux — sessioni persistenti

La connessione cade, il processo muore. Con tmux le sessioni sopravvivono alla disconnessione.

tmux new -s produzione    # nuova sessione chiamata "produzione"
tmux ls                   # lista sessioni attive
tmux attach -t produzione # ricollegati

# tasti dentro tmux (tutti preceduti da Ctrl+B)
# Ctrl+B d        → detach (sessione rimane attiva)
# Ctrl+B c        → nuova finestra
# Ctrl+B n/p      → finestra successiva/precedente
# Ctrl+B %        → split verticale
# Ctrl+B "        → split orizzontale
# Ctrl+B frecce   → muoviti tra i pannelli
# Ctrl+B [        → modalità scroll (q per uscire)
10 / sicurezza

Sicurezza Base

Quello che dovresti fare su ogni nuovo server prima di esporre qualcosa

🛡️ Checklist nuovo server

Urgente

  • Cambia la password di root o disabilitala
  • Crea un utente non-root con sudo
  • Imposta chiavi SSH, disabilita password auth
  • Configura il firewall (ufw / nftables)
  • Aggiorna tutto: apt update && apt upgrade

Importante

  • Installa fail2ban
  • Cambia porta SSH (riduce rumore)
  • Configura unattended-upgrades
  • Limita accesso SSH: AllowUsers
  • Controlla i SUID file periodicamente

🚫 fail2ban — banna i brute forcer

apt install fail2ban

# /etc/fail2ban/jail.local (NON modificare jail.conf)
[DEFAULT]
bantime  = 1h      # banna per 1 ora
findtime = 10m     # finestra di osservazione
maxretry = 5       # tentativi prima del ban

[sshd]
enabled = true
port    = 2222     # se hai cambiato porta
logpath = /var/log/auth.log

[nginx-http-auth]
enabled = true

systemctl enable --now fail2ban

# gestire ban
fail2ban-client status             # panoramica
fail2ban-client status sshd        # dettagli jail sshd
fail2ban-client set sshd unbanip 1.2.3.4  # unban IP
fail2ban-client banned             # tutti gli IP bannati

🔄 Aggiornamenti automatici

apt install unattended-upgrades
dpkg-reconfigure unattended-upgrades   # configura interattivamente

# /etc/apt/apt.conf.d/50unattended-upgrades
# Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}-security"; };
# Unattended-Upgrade::Mail "admin@esempio.it";
# Unattended-Upgrade::Automatic-Reboot "false";

# verifica che funzioni
unattended-upgrade --dry-run --debug

🔍 Audit rapido di sicurezza

# chi può fare sudo
grep -v '^#' /etc/sudoers | grep -v '^$'
grep -r '' /etc/sudoers.d/

# utenti con UID 0 (root) — dovrebbe essere solo root
awk -F: '$3==0' /etc/passwd

# utenti con shell di login
grep -v '/nologin\|/false' /etc/passwd | grep -v '^#'

# porte aperte verso l'esterno
ss -tuln
nmap -sV localhost

# file SUID (potenziale escalation)
find / -perm -4000 -type f 2>/dev/null

# login falliti recenti
lastb | head -20
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head
11 / automazione

Cron, Timer & Automazione

Perché farlo a mano tre volte quando puoi automatizzarlo una volta

crontab — la sintassi che tutti cercano su Google

# sintassi: minuto ora giorno-mese mese giorno-settimana comando
# m    h    dom   month  dow    command
# 0-59 0-23 1-31  1-12   0-7(0=7=dom)

crontab -e          # modifica crontab dell'utente corrente
crontab -l          # mostra crontab corrente
crontab -u mario -e # crontab di un altro utente (da root)

# esempi
0 2 * * *     /usr/local/bin/backup.sh       # ogni notte alle 2:00
*/5 * * * *   /usr/local/bin/check.sh        # ogni 5 minuti
0 9 * * 1-5   /usr/local/bin/report.sh       # lun-ven alle 9:00
@reboot       /usr/local/bin/startup.sh      # solo al boot
@daily        /usr/local/bin/daily.sh        # una volta al giorno
0 */6 * * *   /usr/local/bin/ogni6ore.sh     # ogni 6 ore

# redirigere l'output (altrimenti arriva per email o si perde)
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# crontab di sistema
/etc/crontab       # ha un campo "utente" in più
/etc/cron.d/       # un file per job (consigliato per applicazioni)
/etc/cron.daily/   # script eseguiti ogni giorno
/etc/cron.hourly/  # ogni ora

⏱️ systemd timer — alternativa moderna a cron

Più potente di cron: supporta dipendenze, logging integrato in journald, persistenza (se il server era spento, esegue al riavvio).

# /etc/systemd/system/backup.service [Unit] Description=Backup notturno [Service] Type=oneshot User=backup ExecStart=/usr/local/bin/backup.sh # /etc/systemd/system/backup.timer [Unit] Description=Timer backup notturno [Timer] OnCalendar=*-*-* 02:00:00 # ogni notte alle 2 Persistent=true # esegue se era spento all'orario RandomizedDelaySec=5m # jitter casuale (evita thundering herd) [Install] WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now backup.timer
systemctl list-timers               # tutti i timer attivi e quando scattano
journalctl -u backup.service        # log dell'esecuzione
12 / troubleshooting

Troubleshooting

Quando le cose vanno storte (e vanno sempre storte)

🩺 Diagnosi rapida: il checklist dei 5 minuti

# 1. stato generale
uptime                          # load average, da quanto è su
free -h                         # RAM disponibile
df -h                           # spazio disco

# 2. servizi falliti
systemctl list-units --failed

# 3. errori recenti
journalctl -p err -b            # errori dal boot corrente
dmesg | tail -20                # messaggi kernel recenti

# 4. chi sta usando le risorse
top                             # CPU e RAM in tempo reale
iotop -o                        # I/O disco

# 5. connessioni di rete
ss -tuln                        # porte in ascolto
ss -tp state established        # connessioni attive

🔬 Strumenti diagnostici avanzati

# strace — cosa sta facendo un processo (system call)
strace -p 1247                  # attach a processo esistente
strace -p 1247 -e trace=file    # solo syscall su file
strace -p 1247 -e trace=network # solo syscall di rete
strace comando 2>&1 | head -50 # studia l'avvio di un comando

# lsof — file aperti
lsof -p 1247                    # file aperti dal PID 1247
lsof -u mario                   # file aperti dall'utente mario
lsof -i :80                     # chi usa la porta 80
lsof -i TCP:1:1024              # tutte le porte privilegiate in uso

# dmesg — messaggi kernel
dmesg -T                        # con timestamp leggibili
dmesg -T | grep -i error
dmesg -T | grep -i oom         # OOM killer
dmesg -T | grep -i "I/O error" # errori disco

# trovare cosa occupa una porta
ss -tlnp | grep :80
fuser -n tcp 80                 # PID che usa porta 80

# diagnostica file system
fsck /dev/sda1                  # check filesystem (solo smontato!)
smartctl -a /dev/sda            # salute disco SMART (apt install smartmontools)
badblocks -v /dev/sda           # cerca settori danneggiati (lento)

📜 Problemi comuni e soluzioni

SintomoCausa probabileDove guardare
Server non risponde via SSHDisco pieno → sshd non riesce a scriveredf -h, /var/log occupazione
Load average altissimoProcesso in loop, I/O wait, OOMtop, iotop, dmesg | grep oom
Servizio non partePorta già in uso, config sbagliata, permessisystemctl status servizio, journalctl -u servizio
Permission deniedOwner/group sbagliato, SELinux/AppArmorls -la, id, aa-status
Cron non eseguePATH diverso, nessun output, utente sbagliato/var/log/syslog | grep CRON, testa il comando manualmente
Disco pieno ma df mostra spazioInode esauriti, file cancellato ma ancora apertodf -i (inode), lsof | grep deleted
Processo zombieIl processo padre non fa wait()ps -ef --forest, riavvia il padre

🧪 Il caso classico: disco pieno con file cancellati

Hai cancellato un file di log enorme, df dice ancora "pieno". Motivo: il processo che aveva il file aperto lo tiene ancora in uso — lo spazio viene liberato solo quando il processo rilascia il file descriptor.

# trova file cancellati ma ancora aperti (quindi ancora occupano spazio)
lsof | grep deleted
nginx   892  www-data  10w  REG  ... /var/log/nginx/access.log (deleted)

# soluzioni
systemctl reload nginx     # riapre il file di log (preferito)
kill -HUP 892              # oppure SIGHUP al processo
# in extremis
: > /proc/892/fd/10        # svuota il file tramite FD (senza riavviare)
🤔 La verità sul troubleshooting Linux

Il 90% dei problemi rientra in queste categorie: disco pieno, permessi sbagliati, servizio non avviato/crashato, porta già in uso. Prima di fare cose complesse, controlla queste quattro cose nell'ordine. Poi leggi i log. Poi chiedi a Google con l'errore esatto copiato e incollato. Poi considera che forse il problema è il DNS.