104 comandos que se escriben de verdad, con cada opción explicada — por objetivo
Buscar, procesar en lote, empaquetar: un parámetro mal recordado suele costar borrar el archivo equivocado u omitir directorios ocultos
-l formato largo: permisos, propietario, tamaño, fecha
-a incluir . y .. y todos los archivos ocultos
-h tamaños con unidad (1.2K en lugar de 1234); solo funciona con -l
-t ordenar por fecha de modificación, lo más nuevo arriba
-r invertir el orden; con -t deja lo más antiguo arriba
-S ordenar por tamaño, lo más grande primero: más directo que -t para ver quién ocupa espacio
--time-style=long-iso mostrar 2026-10-29 09:18 en lugar del formato por defecto
# el archivo más reciente arriba, con tamaño y fecha ls -laht --time-style=long-iso # encontrar por tamaño los que más espacio ocupan (-S) ls -lhS /var/log | head
-name coincidencia por nombre de archivo, distingue mayúsculas; -iname las ignora
-type f solo archivos regulares; -type d solo directorios; -type l enlaces simbólicos
-mtime +7 modificado hace más de 7 días; -mmin -30 en los últimos 30 minutos
-size +50M mayor que 50M; -size -1k menor que 1k
-maxdepth 1 no entrar en subdirectorios: añadirlo primero al depurar
-ls ejecuta ls -dils sobre cada resultado: tamaño, inode y fecha de una vez
-delete borra directamente (solo directorios vacíos); -exec ... \; ejecuta un comando por resultado
-prune omite un directorio; debe combinarse con -path
-o significa «o» al encadenar condiciones; -print fuerza la salida (obligatorio con -prune)
# archivos grandes modificados en los últimos 30 minutos (para investigar un aumento repentino de disco) find /var -type f -mmin -30 -size +50M -ls # borrar en lote los logs de hace más de 7 días find /var/log -name '*.log.*' -mtime +7 -delete # buscar omitiendo .git y node_modules find . -path ./node_modules -prune -o -name '*.js' -print
-r recursivo en directorios; -R recursivo siguiendo enlaces simbólicos
-i ignorar mayúsculas; -n mostrar número de línea; -v invertir, solo las líneas que no coinciden
-E regex extendida (| exige -E, si no se toma como literal)
-l solo nombres de archivo, útil para acotar antes de leer
-A3 / -B3 / -C3 mostrar 3 líneas después / antes / alrededor de la coincidencia
--include='*.conf' restringir por sufijo; --exclude-dir omitir directorios
-w palabra completa, para que cat no encuentre category
-rn es la pareja diaria: recursivo más número de línea, si no no hay forma de ubicarla
# buscar un upstream en la configuración de nginx, con 3 líneas de contexto grep -rn -C3 'upstream' /etc/nginx --include='*.conf' # coincidencia de cualquiera de varias palabras clave (requiere -E) grep -rnE 'timeout|refused|reset' /var/log/app/error.log # listar solo los nombres de archivo con la configuración y revisarlos uno a uno grep -rli 'proxy_pass' /etc/nginx
-c crear; -x extraer; -t listar sin extraer; -z gzip; -j bzip2
-f lleva el nombre del archivo, por eso -f va siempre al final
-v mostrar el progreso; frena mucho los paquetes grandes, redirigirlo en scripts
-C extraer en un directorio (debe existir)
--exclude omitir rutas al empaquetar; el patrón se compara con rutas relativas
-czf / -xzf / -tzf son las tres combinaciones que basta con memorizar
Antes de extraer, pasar -t una vez: evita que un tarbomb sobrescriba el directorio actual
# empaquetar excluyendo el directorio de caché tar -czf app.tar.gz --exclude='node_modules' --exclude='.git' -C /srv app # ver el contenido primero y luego extraer en el directorio indicado tar -tzf app.tar.gz | head -20 tar -xzf app.tar.gz -C /tmp/restore
rm -rf borra recursivo y forzado, sin confirmación: con la variable vacía, rm -rf "$DIR/" se convierte en rm -rf /
-f fuerza sin preguntar; -r entra en directorios. Juntas son la combinación que más desastres causa
cp -a modo archivo: conserva permisos, fechas y enlaces simbólicos; la opción para copias de seguridad
cp -n no sobrescribe lo existente; igual mv -n (-n es no-clobber)
mv entre sistemas de archivos es copiar y borrar, no atómico: una interrupción deja ambos lados a medias
Un archivo borrado que un proceso mantiene abierto no libera espacio hasta que el proceso sale
test -n "$DIR" comprueba que la variable no está vacía: protección obligatoria antes de cualquier rm
-- termina el análisis de opciones, para que un nombre que empieza por - no se lea como parámetro
# borrado seguro: confirmar primero que la variable no esté vacía y luego borrar [ -n "$DIR" ] && rm -rf -- "$DIR" # copia de seguridad completa conservando los atributos cp -a /srv/app /backup/app-$(date +%F)
df -h uso por punto de montaje; -i uso de inodos (con la tabla de inodos llena tampoco se puede escribir)
du -sh tamaño total de un directorio; -s da un solo total sin desglose, -h legible para humanos
du -sh * | sort -h el mayor del directorio actual; sort -h ordena bien 1K/2M/3G
--max-depth=1 solo un nivel, mucho más rápido que la recursión completa
Una gran diferencia entre du y df casi siempre son archivos borrados pero aún abiertos por un proceso
2>/dev/null descarta los errores de permisos, que si no inundan la salida
# descender nivel a nivel desde la raíz y entrar en el que crezca de golpe du -h --max-depth=1 / 2>/dev/null | sort -h | tail -10 # archivos borrados retenidos por procesos (cuando df está lleno y du no) lsof +L1 2>/dev/null | head
ln -s DESTINO ENLACE: el destino va primero; al revés se crea un enlace roto
-s crea un enlace simbólico; sin -s es duro (no cruza sistemas de archivos ni sirve para directorios)
-f sobrescribe un enlace existente
-n con -f reemplaza un enlace que apunta a un directorio en lugar de anidarlo dentro
-sfn es la combinación estándar para cambiar de release (current apunta a la nueva)
El simbólico guarda una ruta y se rompe si el destino se mueve; el duro comparte inodo, borrar uno no afecta al otro
ln -sfn /srv/app/releases/20261029 /srv/app/current
stat da tamaño, inodo, modo y tres fechas de una vez: la respuesta directa a «¿este archivo se modificó de verdad?»
-c formato propio: %y fecha de modificación, %s bytes, %U:%G propietario, %a modo octal, %n nombre
-L sigue el enlace y describe el destino (sin -L se describe el enlace mismo)
Tres fechas: Access lectura, Modify cambio de contenido, Change cambio de metadatos (modo/propietario)
-f información del sistema de archivos (tamaño de bloque, inodos totales y libres), complementa a df
touch -d '2026-10-01 08:00' ARCHIVO fija la fecha; con find -newer se acota el rango de cambios
file informa el tipo real, más fiable que la extensión, que se puede cambiar a voluntad
stat -c '%a' es la forma más rápida de confirmar los bits de modo reales
stat -c '%y %s %U:%G %a %n' /etc/nginx/nginx.conf stat -L -c '%y %s' /srv/app/current # ver el archivo real al que apunta el enlace simbólico file /usr/local/bin/app
Editar configuraciones, estadísticas por columna, armar líneas de comando: lo de «editar 200 líneas a mano» es cosa de sed y awk
-i edita el archivo en el sitio: en producción, ejecutar primero sin -i y revisar la salida antes de aplicarlo
-i.bak deja una copia .bak al editar, para revertir al instante; conviene hacerlo hábito
-n no imprime automáticamente; con p muestra solo las líneas pedidas
-E regex extendida (+ ? | ( ) sin escapar); en sistemas viejos es -r, mismo efecto
-e encadena expresiones; también se separan con punto y coma dentro de una sola
La g de s/viejo/nuevo/g sustituye todas las apariciones de la línea; sin g solo la primera
Rangos por dirección: '/^server/,/}/ s/a/b/' cambia solo dentro de un bloque de configuración
\1 \2 referencian grupos de captura; & es la coincidencia completa
El orden importa: sed -E -i.bak 's/x/y/' ARCHIVO
sed -n '1,20p' /etc/nginx/nginx.conf # ver solo las primeras 20 líneas sed -i.bak 's/listen 80;/listen 8080;/' /etc/nginx/sites-enabled/app sed -E -i.bak 's/^server_name[[:space:]]+([^;]+);/server_name \1.bak;/' app.conf sed '/^[[:space:]]*#/d;/^[[:space:]]*$/d' app.conf | head # eliminar comentarios y líneas vacías
-F fija el separador: -F: corta por dos puntos, -F'\t' por tabulador; sin -F, por espacios consecutivos
'{print $1,$3}' toma las columnas 1 y 3; $0 es la línea entera; NF el número de campos; NR el de línea
El filtro va delante del programa: '$9==500' significa que la columna 9 vale 500
Forzar comparación numérica con +0: $9+0 >= 500; si no, la comparación de cadenas da resultados raros
'{s+=$7} END {print s}' es la plantilla de suma; para contar, '{c++} END {print c}'
Estadística con array: {c[$1]++} END {for (k in c) print k, c[k]}
-v pasa variables externas: awk -v lim=500 '$3>lim'; -v va antes del programa
-F y -v los interpreta el propio awk, así que preceden al programa entrecomillado
En los ejemplos: sort -rn ordena numérico desc, uniq -c cuenta, ss -tn lista conexiones TCP
awk -F: '$3>=1000 {print $1, $3}' /etc/passwd
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
awk -v lim=500 '{ if ($9+0 >= lim) print NR": "$7 }' access.log
ss -tn | awk 'NR>1 {c[$1]++} END {for (k in c) print k, c[k]}'sort -n orden numérico (el orden léxico por defecto pone 10 antes que 9)
sort -h entiende 1K/2M/3G: obligatorio con la salida de du y df
sort -r invierte; -u ordena y deduplica en una pasada
-k2 ordena por la columna 2, -k3 igual; varias claves como -k1,1 -k2,2
-t: fija el separador en dos puntos (por defecto corta por espacios)
uniq -c cuenta repeticiones consecutivas: por eso hay que ordenar antes, o los mismos valores salen en bloques
uniq -d solo las líneas repetidas, uniq -u solo las únicas
La receta de Top N: sort | uniq -c | sort -rn | head
En el ejemplo, la -s de du -sh da solo el total sin desglose (ver du / df)
du -sh /var/log/* | sort -h | tail -5
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
sort -t: -k3 -n /etc/passwd | headcut -d: -f1,7 toma campos por separador: -d lo fija, -f elige campos (admite varios)
cut -c1-19 corta por posición de carácter, útil para la marca de tiempo de logs de ancho fijo
cut solo admite un separador; con salida alineada por espacios corta mal, usar awk
tr 'a-z' 'A-Z' mapea carácter a carácter; para sustituir cadenas hace falta sed
tr -d borra caracteres; lo más común es tr -d '\r' para quitar el fin de línea de Windows
tr -s comprime caracteres repetidos en uno, típico para dejar los espacios en uno
tr solo lee de la entrada estándar, no acepta nombres de archivo: redirigir con <
En los ejemplos: df -h legible, cut -f5 toma el campo 5 (Use%), sort -u deduplica
cut -d: -f1,7 /etc/passwd | head cut -c1-19 /var/log/nginx/access.log | head -3 tr -d '\r' < crlf.txt > lf.txt df -h | tr -s ' ' | cut -d' ' -f5 | sort -u
xargs convierte cada línea de la entrada en argumentos del comando: así funciona find | xargs rm
-0 va con find -print0: los nombres con espacios o saltos de línea no se parten (obligatorio con archivos subidos por usuarios)
find -print0 termina cada resultado con \0, justo lo que espera xargs -0
-n1 pasa un argumento por ejecución: más seguro y se ve cuál falló
-I {} es un marcador para poner el argumento en medio: xargs -I {} cp {} /backup
-p pregunta antes de ejecutar; -t imprime el comando primero: usar -t cuando no está claro qué se va a armar
-r no ejecuta con entrada vacía (extensión GNU); si no, una lista vacía lanza un rm igual
-P4 ejecuta cuatro procesos en paralelo: varias veces más rápido al comprimir o descargar en lote
cp --parents recrea la estructura de directorios, útil al restaurar copias
En los ejemplos, -name / -mtime / -type de find filtran y la -f de rm -f fuerza sin preguntar
find /var/log -name '*.log.*' -mtime +7 -print0 | xargs -0 -r rm -f
find /srv/uploads -type f -print0 | xargs -0 -I {} cp --parents {} /backup
printf '%s\n' a b c | xargs -n1 -t echotee escribe la entrada al archivo y a la salida estándar a la vez: el recurso para dejar constancia
-a añade en lugar de truncar; para logs, siempre
Por qué sudo tee: en sudo echo x > /etc/f la redirección la hace tu shell y falla por permisos
-i ignora las señales de interrupción, así una captura larga no se corta con Ctrl-C
Antes de la tubería hace falta 2>&1 para unir stderr, o los errores no llegan a tee
Poner marca de tiempo al flujo: ... | ts o | awk '{print strftime("%F %T"), $0}'
./deploy.sh 2>&1 | tee -a /var/log/deploy-$(date +%F).log echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
jq procesa el JSON de las respuestas y es mucho más fiable que grep: lee por estructura, no por líneas
. lee campos: .data .items[0].name; .[] recorre arrays
-r imprime cadenas sin comillas: obligatorio al pasarlas a variables o a otra tubería
-c deja cada resultado en una línea, cómodo para contarlo con sort / uniq
'keys' lista los campos del objeto; 'length' el tamaño del array; '..' todas las rutas
select() filtra: '.[] | select(.status != "ok")'
-C colorea para leer; -M fuerza salida plana para archivos y scripts
jq formatea por defecto; -e convierte el resultado en código de salida para scripts
En los ejemplos curl -s silencia la barra de progreso, que si no ensucia el JSON
curl -s https://api.example.com/v1/tools | jq -r '.data[].name'
curl -s 'https://api.example.com/v1/list?page=1' | jq -c '.items[] | {id, name}'
jq -r '.[] | select(.status != "ok") | .id' resp.jsondiff -u salida unificada, el formato de parche que patch aplica directamente
diff -r compara dos árboles; -q solo dice qué archivos difieren
diff -y muestra en paralelo; -W 200 fija el ancho, cómodo para comparar configuraciones a ojo
wc -l líneas (la métrica habitual en logs); -c bytes; -w palabras
wc añade el nombre del archivo, así que en scripts conviene awk '{print $1}'
comm sirve mejor para la diferencia entre dos listas ordenadas, pero ambas deben ordenarse antes
comm -13 a b muestra las líneas solo de b; revisar la lógica de columnas antes de fiarse
Ordenar y deduplicar ambos lados primero; si no, «todo distinto» suele ser solo otro orden
diff -u /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf | head -40 diff -rq /srv/app/releases/20261028 /srv/app/releases/20261029 wc -l /var/log/nginx/access.log sort a.txt > a.s; sort b.txt > b.s; comm -13 a.s b.s # líneas que solo están en b
Entrar, claves, transferir, reanudar: lo que más tiempo consume suele ser llevar el archivo hasta allí
ssh USUARIO@HOST es el acceso básico; puerto 22 por defecto
-p 2222 fija el puerto (ojo: scp y sftp usan -P mayúscula, no confundirlos)
-i indica la clave privada; por defecto busca ~/.ssh/id_ed25519 e id_rsa
-t fuerza un pseudo-terminal: obligatorio para sudo, top y otras órdenes interactivas
-v traza el handshake y la autenticación: aclara si el fallo es de red o de credenciales; -vvv da más detalle
-o pasa opciones: -o ConnectTimeout=5 evita bloqueos, -o BatchMode=yes prohíbe toda interacción (obligatorio en scripts)
Un solo comando remoto: ssh HOST 'cmd', sin terminal, ideal para chequeos en lote
-J salta por un bastión: ssh -J bastion@1.2.3.4 deploy@10.0.0.5 (forma antigua: -o ProxyCommand)
En los ejemplos, la -h de df -h es legible para humanos (ver du / df)
ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@10.0.0.5 ssh -o ConnectTimeout=5 -o BatchMode=yes 10.0.0.5 'uptime; df -h /' ssh -v -t 10.0.0.5 'sudo systemctl restart app' ssh -J bastion@1.2.3.4 deploy@10.0.0.5
ssh-keygen -t ed25519 genera el par de claves (más corto y rápido que rsa; todo sistema moderno lo admite)
-t elige el tipo: ed25519 preferido; rsa solo por necesidad en sistemas viejos, con -b 4096 para la longitud
-C añade un comentario (uso o correo) para distinguir varias claves
-f nombra el archivo de salida y evita sobrescribir la clave por defecto, lo que rompería el acceso ya configurado
-N '' deja la contraseña vacía para automatizar; de forma interactiva basta con pulsar Enter
ssh-copy-id añade la clave pública al ~/.ssh/authorized_keys remoto y corrige los permisos
-i (de ssh-copy-id) elige qué clave pública enviar; por defecto toma id_*.pub
-R borra de known_hosts la huella antigua de un host: tras reinstalar hay que ejecutarlo o aparece REMOTE HOST IDENTIFICATION HAS CHANGED
-lf muestra huella y tipo, para confirmar que ambas máquinas tienen la misma clave
La clave privada debe ser 600 y el directorio 700, o ssh se niega a usarla
ssh-keygen -t ed25519 -C 'deploy@icetools' -f ~/.ssh/id_deploy -N '' ssh-copy-id -i ~/.ssh/id_deploy.pub deploy@10.0.0.5 ssh-keygen -R 10.0.0.5 ssh-keygen -lf ~/.ssh/id_deploy.pub
Con esto, ssh app equivale a ssh -p 2222 deploy@10.0.0.5, sin teclear la línea larga cada vez
Host es el alias y el ámbito de todo lo que sigue
HostName dirección real; User usuario; Port puerto; IdentityFile ruta de la clave
IdentitiesOnly yes ofrece solo esa clave y evita que el agente pruebe una docena (demasiados intentos pueden bloquear la IP)
ProxyJump es el bastión, equivale a -J y lo heredan scp / rsync
ServerAliveInterval 30 manda un latido cada 30 s; NAT y los balanceadores cortan conexiones inactivas, imprescindible en tareas largas
ServerAliveCountMax 3 corta tras tres latidos fallidos, junto con el intervalo anterior
Host * al final del archivo lleva los valores globales (latido, tiempos de espera)
Permisos: config 600 y ~/.ssh 700, o aparece Bad owner or permissions
# ~/.ssh/config (chmod 600)
Host app
HostName 10.0.0.5
User deploy
Port 2222
IdentityFile ~/.ssh/id_deploy
IdentitiesOnly yes
ServerAliveInterval 30
ServerAliveCountMax 3
Host db
HostName 10.0.0.6
ProxyJump app
Host *
ServerAliveInterval 30-L reenvío local: ssh -L 8080:127.0.0.1:80 HOST lleva el 80 remoto al 8080 local
-R reenvío inverso: expone un puerto local en el remoto para alcanzar un servicio interno
-D reenvío dinámico: levanta un proxy SOCKS (con curl --socks5 o el navegador)
-N solo reenvía, sin shell; -f lo manda a segundo plano: casi siempre van juntos
-g permite que otros hosts usen el puerto reenviado (por defecto escucha solo en 127.0.0.1)
La dirección intermedia de -L se resuelve desde el lado remoto: 127.0.0.1 allí es el propio host remoto
Cuando la base de datos no admite conexión directa: traer el puerto con -L y conectar a 127.0.0.1
Si el puerto local está ocupado, elegir otro; lsof -i :8080 muestra quién lo tiene
ssh -N -f -L 15432:127.0.0.1:5432 deploy@10.0.0.5 ssh -N -L 8080:10.0.0.6:80 app ssh -D 1080 -N -f app ssh -g -L 8080:127.0.0.1:80 app
scp ORIGEN DESTINO: el orden decide subida o bajada; local primero sube, remoto primero baja
-P mayúscula fija el puerto (ssh usa -p minúscula: la confusión clásica)
-r recorre directorios; sin -r copiar un directorio falla directamente
-i elige la clave privada; -o pasa opciones de ssh, como -o ConnectTimeout=5
-C comprime en tránsito: se nota con texto y logs, no sirve con archivos ya comprimidos
-l 8000 limita el ancho de banda en Kbit/s (8000 ≈ 1MB/s) para no saturar producción
-p minúscula conserva fechas y permisos; no tiene relación con -P, no confundirlas
scp no reanuda: una caída en un archivo grande implica empezar de cero; para varios GB, usar rsync -P
scp -P 2222 app.tar.gz deploy@10.0.0.5:/srv/ scp -r -i ~/.ssh/id_deploy ./dist deploy@10.0.0.5:/srv/app/ scp -C -l 8000 backup.tar.gz deploy@10.0.0.5:/backup/ scp -p deploy@10.0.0.5:/var/log/app/error.log ./
sftp USUARIO@HOST abre una sesión interactiva: listar mientras se transfiere da más control que copiar a ciegas
-P mayúscula para el puerto y -i para la clave, igual que scp
get ARCHIVO_REMOTO descarga; put ARCHIVO_LOCAL sube; get -r trae un directorio completo
reget reanuda una descarga y reput una subida: la ventaja real sobre scp
-p (de get/put) conserva permisos y fechas
mget / mput admiten comodines, pero confirman cada archivo: ejecutar antes prompt off
ls / lls listan remoto y local; cd / lcd cambian de directorio remoto y local
!df -h ejecuta una orden local: comprobar el espacio antes de traer algo grande
bye / exit salen; un script con heredoc automatiza la transferencia
sftp -P 2222 -i ~/.ssh/id_deploy deploy@10.0.0.5 <<'EOF' get -r /srv/app/logs ./ reget backup.tar.gz put ./dist/app.tar.gz /srv/ EOF
-a modo archivo (= -rlptgoD): conserva permisos, fechas, enlaces y grupo; la opción diaria
-v muestra el progreso; -z comprime en tránsito, imprescindible por internet
-P equivale a --partial --progress: conserva el archivo a medias y muestra avance, el interruptor de reanudación
--partial conserva lo ya transferido para continuar luego; sin él cada ejecución empieza de cero
--append solo añade: vale para logs que únicamente crecen, no para archivos que cambian
--delete hace que el destino refleje el origen (borra lo sobrante): ensayar antes con -n
-n y --dry-run son lo mismo: imprimen lo que harían sin tocar nada
-e 'ssh -p 2222' elige el shell remoto y el puerto; rsync no tiene opción de puerto propia
--exclude descarta un patrón; --exclude-from lee una lista de un archivo
--bwlimit=2000 limita en KB/s; --checksum compara contenido en vez de tamaño y fecha
--info=progress2 muestra una línea de progreso total, mejor que -P en árboles grandes
La trampa mayor: la barra final en el origen copia el CONTENIDO; sin barra se copia el directorio
rsync -avzP ./dist/ deploy@10.0.0.5:/srv/app/dist/ rsync -avzP -e 'ssh -p 2222' deploy@10.0.0.5:/var/log/app/ ./logs/ rsync -avn --delete /srv/data/ /backup/data/ # simulación: revisar bien antes de ejecutar de verdad rsync -avz --bwlimit=2000 --exclude='*.tmp' ./ /mnt/nas/app/
wget URL descarga al directorio actual; -O nombra la salida y -O - escribe a la salida estándar
-c reanuda (curl necesita -C -): relanzar el mismo comando tras una caída y continúa
-q modo silencioso; --progress=dot:giga muestra un avance compacto
--limit-rate=1m limita la velocidad, admite sufijos k/m; --tries=3 fija los reintentos
-T 30 tiempo de conexión en segundos, para no quedarse colgado; --timeout es su forma larga
-r replica un sitio; -np impide subir a directorios padre (y arrastrar todo el dominio)
--no-check-certificate omite la validación del certificado: solo para diagnóstico interno, nunca en un script
Para cabeceras propias, cookies o sesión, curl es más flexible que wget
wget -c https://example.com/big.iso wget -O - https://example.com/install.sh | head -40 # ver el contenido antes de decidir si ejecutarlo wget --limit-rate=1m --tries=3 -c -O app.tar.gz https://example.com/app.tar.gz
Modos numéricos, bits especiales, sudo: los fallos de permisos suelen verse como «la contraseña es correcta pero no entra»
Numérico: 4 lectura / 2 escritura / 1 ejecución; los dígitos son propietario / grupo / otros
755 para directorios (rwxr-xr-x); 644 para archivos (rw-r--r--)
600 solo el propietario lee y escribe: obligatorio en claves privadas
Simbólico: u/g/o/a indica a quién, +/-/= la acción, X añade ejecución solo a directorios
-R recursivo; con directorios conviene a+X en vez de a+x para no hacer ejecutables los archivos
go-w quita escritura a grupo y otros, en la misma pasada que a+X
Tras un cambio recursivo, revisar con ls -l un archivo y un directorio: casi siempre se pasó de largo
chmod 600 ~/.ssh/id_rsa chmod -R a+X,go-w /srv/app/public ls -l /srv/app/public | head # tras el cambio recursivo, verificar una muestra
chown usuario:grupo RUTA; el punto también sirve como separador
Para cambiar solo el grupo: chown :grupo o chgrp
-R recursivo; con -h afecta al enlace y no a su destino
--reference=ARCHIVO copia propietario y modo de un archivo existente, útil al sincronizar en lote
Solo root cambia el propietario; un usuario solo puede cambiar el grupo de sus archivos, y debe pertenecer a ese grupo
Ante un Permission denied en un volumen de contenedor, comparar el uid del directorio anfitrión con el usuario del contenedor
chown -R www-data:www-data /var/www/app chown --reference=/srv/app/a /srv/app/b
4000 SUID: se ejecuta con la identidad del propietario (así funciona /usr/bin/passwd)
2000 SGID: se ejecuta con el grupo; en un directorio, los archivos nuevos heredan ese grupo
1000 sticky: dentro del directorio solo el propietario puede borrar (/tmp funciona así)
Una s o t en ls indica el bit activo; S/T mayúscula significa activo pero sin ejecución
-perm -4000 significa «el modo contiene SUID»; -perm es el test de permisos de find
-xdev se queda en un sistema de archivos: sin él find recorre NFS y montajes de contenedores y se arrastra
-ls imprime los resultados como ls -dils, con tamaño y fecha a la vez
-type f se limita a archivos regulares y descarta directorios y enlaces
Para auditar escalada de privilegios, buscar primero SUID: find / -xdev -perm -4000 -type f
# auditoría: listar todos los programas SUID find / -xdev -perm -4000 -type f -ls 2>/dev/null # directorio compartido: colaboración dentro del grupo y solo el propietario puede borrar chmod 2770 /srv/share
umask son los bits que se RESTAN, no el modo en sí
022 deja directorios en 755 y archivos en 644 (los archivos nunca nacen con ejecución)
077 deja directorios en 700 y archivos en 600: lo correcto para material de claves
-S imprime el modo resultante en forma simbólica (rwxr-xr-x), más claro que los dígitos
Temporal: umask 077 afecta solo al shell actual
Permanente: añadirlo a /etc/profile o al ~/.bashrc del usuario
Si una clave recién creada la lee todo el mundo, mirar antes el umask
umask # ver el valor actual umask 077 # válido en la sesión actual umask -S # mostrar los permisos predeterminados reales en forma simbólica
sudo -i abre un shell de login de root nuevo (carga el entorno de root)
sudo -s abre un shell de root con tu entorno y tu PATH
sudo -u USUARIO ejecuta como otro usuario, habitual al probar servicios
sudo -l lista lo que puedes ejecutar: el primer paso cuando algo se deniega
su - pasa a root con su entorno; sin el guion conservas el tuyo y el PATH puede fallar
Editar sudoers con visudo, que valida la sintaxis; -c solo comprueba sin guardar
Editar /etc/sudoers a mano puede dejarte fuera: el clásico autogol de operación
sudo -l sudo -u www-data /usr/bin/env # ver el entorno de la cuenta de servicio visudo -c # solo comprobar la sintaxis
useradd -m crea el directorio home; -s fija el shell; -G añade grupos suplementarios
Sin -m no hay home y muchas herramientas fallan después del login de forma desconcertante
usermod -aG GRUPO USUARIO: sin -a el usuario sale de todos los demás grupos (-a es append)
-G toma la lista de grupos y se combina con -a como -aG
id USUARIO muestra uid/gid y todos los grupos; groups USUARIO solo los grupos
userdel -r borra también el home; sin -r quedan archivos huérfanos
Los cambios de grupo no afectan a sesiones abiertas: hay que volver a entrar
useradd -m -s /bin/bash deploy usermod -aG docker deploy id deploy
Quién consume CPU, quién tiene el archivo, cómo pararlo con elegancia: el error típico aquí es recurrir a kill -9 demasiado pronto
ps aux es estilo BSD: %CPU, %MEM, STAT, COMMAND; la opción diaria
ps -ef es estilo System V: UID, PID, PPID, C, STIME, CMD; muestra el proceso padre
-e selecciona todos los procesos (estilo System V); -o elige las columnas
ps -aux también corre, pero significa otra cosa: no convertirlo en costumbre
--sort=-%cpu ordena por CPU descendente (el menos significa descender); las comas encadenan claves
--sort=-rss ordena por memoria real; en STAT, Z es zombi, D ininterrumpible, R en ejecución
RSS es el uso real; VSZ es espacio de direcciones y apenas sirve
ps aux --sort=-%cpu | head ps -eo pid,ppid,rss,pcpu,cmd --sort=-rss | head
En top, P ordena por CPU, M por memoria, T por tiempo acumulado
1 despliega cada núcleo: distingue un núcleo saturado de una carga global
c muestra la línea de comando completa en vez de truncarla
H alterna hilos; u filtra por usuario; k mata procesos
-b es modo lote y -n1 imprime una sola instantánea (-n es el número de iteraciones)
-p limita a unos PID separados por comas; la -d, de pgrep une varios PID con comas
El load average son los promedios de 1/5/15 minutos; por encima del número de núcleos hay cola
top -b -n1 | head -15 # tomar una instantánea top -p $(pgrep -d, nginx) # ver solo el proceso indicado
kill envía TERM(15) por defecto: el proceso puede cerrar y terminar de escribir
-15 equivale a -TERM; la forma con nombre se lee mejor y es la primera que hay que usar
-9 es KILL: el núcleo lo reclama al instante y el proceso no limpia nada
-1 o -HUP se usa para recargar configuración (nginx y sshd lo admiten)
-0 no envía señal, solo comprueba si existe: el código de salida es la respuesta
kill toma PID, no nombres; para nombres usar pkill o killall
pkill -HUP nginx señala todos los procesos nginx por nombre
Siempre TERM, esperar unos segundos y luego KILL
kill -TERM 1234 && sleep 3 kill -0 1234 && kill -9 1234 pkill -HUP nginx # recargar la configuración
& solo lo manda a segundo plano: al cerrar el terminal llega SIGHUP y muere
nohup ignora SIGHUP y escribe en nohup.out por defecto (mejor redirigir explícitamente)
2>&1 une stderr con stdout, o los errores se pierden
< /dev/null apunta la entrada al dispositivo nulo para que el proceso no se bloquee leyendo el terminal
setsid lo separa de la sesión por completo: más contundente que nohup
disown saca un trabajo ya en segundo plano de la tabla del shell
Para un servicio duradero usar systemd; nohup solo para una verificación rápida
nohup ./run.sh > /var/log/run.log 2>&1 & setsid ./worker.sh < /dev/null > /var/log/worker.log 2>&1 &
lsof -i :8080 muestra quién tiene el puerto 8080 (-i filtra por dirección de red)
-p PID lista todos los archivos abiertos por un proceso
-u USUARIO filtra por usuario; -n evita resolver nombres, más rápido y sin esperas de DNS
+D /ruta recorre un directorio para ver qué procesos lo tienen abierto (lento en árboles grandes)
+L1 lista archivos con enlace menor que 1, borrados pero aún abiertos: el culpable del disco sin liberar
Encadenar sort -k7 -n ordena numéricamente por la columna 7 (SIZE/OFF)
Sin lsof, usar fuser -v o revisar /proc/PID/fd
lsof -i :8080 lsof -n +L1 | sort -k7 -n | tail # ver por tamaño los archivos borrados retenidos
nice va de -20 (máxima prioridad) a 19 (mínima); un usuario normal solo puede subir el número
nice -n 19 CMD arranca con prioridad baja: ideal para copias y compresión
-n lleva el valor de nice, no un contador
renice -n 10 -p PID cambia un proceso en marcha; -p indica el PID y -u actúa por usuario
taskset -c 0-3 fija núcleos: -c toma la lista y reduce la disputa entre instancias
En taskset -cp 0-3 1234, -p significa «operar sobre un proceso existente» y el PID va después
Bajar prioridad no crea capacidad: solo protege al trabajo importante
En los ejemplos, -czf de tar: -c crea, -z comprime con gzip, -f nombra el archivo (ver tar)
nice -n 19 tar -czf backup.tar.gz /srv/data renice -n 10 -p 1234 taskset -cp 0-3 1234
watch repite el comando cada 2 segundos y limpia la pantalla: se ve el cambio, no una foto
-n 1 refresca cada segundo; -n toma los segundos (admite 0.5)
-d resalta lo que cambió entre dos ejecuciones: se localiza el movimiento al instante
-d=cumulative mantiene resaltado lo que alguna vez cambió: útil para picos intermitentes
-t oculta la cabecera (comando e intervalo) y deja la pantalla limpia
Entrecomillar el comando: si no, el shell parte la tubería y watch solo recibe la primera mitad
No usarlo con métricas de alta frecuencia (conexiones, QPS): vmstat 1 o sar muestran lo que watch pierde
En los ejemplos, ss -s resume conexiones y wc -l cuenta líneas (esa l es de lines)
watch -n 1 -d 'ss -tn state established | wc -l' watch -d=cumulative -t 'ss -s'
screen -S deploy abre una sesión con nombre: cerrar el terminal o perder la red no la detiene
-list lista las sesiones (también -ls): comprobar primero si hay una antigua en estado Attached
-r deploy reengancha; si dice Attached, otro la tiene: usar -d -r para tomarla
Ctrl-a d desengancha en screen; Ctrl-a " lista ventanas; Ctrl-a k mata la actual
tmux new -s deploy crea una y tmux attach -t deploy la retoma (-t la nombra)
En tmux, Ctrl-b d desengancha y Ctrl-b [ entra en modo copia para volver atrás en la salida
Abrir sesión antes de migraciones, copias o transferencias grandes: la red caída no detiene el trabajo, solo lo oculta
screen -S deploy # después pulsar Ctrl-a d para desacoplarse screen -list screen -r deploy tmux new -s deploy
Cuando un servicio no responde, separar «no escucha», «no permitido» y «no alcanzable»: comprobarlos en mal orden hace perder mucho tiempo
-t TCP; -u UDP; -l solo escucha; -n numérico (sin resolver servicios, más rápido); -p muestra el proceso
-tulnp es la combinación diaria: cinco letras, cinco cometidos
-tnp sin la l incluye el proceso de las conexiones establecidas: la revisión habitual de conteos
-a incluye conexiones establecidas; -s da el resumen: lo primero cuando los conteos se disparan
state established filtra por estado, más preciso que buscar ESTAB (que es solo la abreviatura de la columna)
netstat está obsoleto y suele faltar en sistemas modernos: usar ss
Escuchar en 127.0.0.1 no es lo mismo que en 0.0.0.0: lo primero solo admite conexiones locales, la causa número uno de «el puerto está abierto pero no conecto»
ss -tulnp ss -tnp state established | head ss -s
-s silencioso: sin barra de progreso ni errores; casi obligatorio en scripts
-S va con -s para volver a mostrar los errores: -sS significa «callado, pero avisa si falla»
-i incluye las cabeceras de respuesta; -I solo cabeceras (envía HEAD, sin cuerpo)
-v traza handshake y petición completos: muestra en qué paso se atasca
-L sigue redirecciones: imprescindible al depurar una cadena 301/302
-X fija el método; -d envía cuerpo (POST por defecto); -H añade cabeceras
-o /dev/null descarta el cuerpo y con -w deja solo código y tiempos
-w '%{http_code} %{time_total}' da salida propia, más rápido que mirar a ojo
-m 10 limita la petición entera a 10 s; --connect-timeout 3 solo la fase de conexión
--resolve host:443:IP fija la IP y evita el DNS: sirve para saber si el problema es de resolución
-x http://proxy:8080 usa proxy; --noproxy '*' fuerza conexión directa (casi siempre necesario al probar en local)
-k omite la validación del certificado: solo para diagnóstico, nunca en un script
Dentro de -w, \n es un salto de línea; sin él todo queda en una sola línea
curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/
curl -m 10 --connect-timeout 3 -sS -o /dev/null \
-w 'dns:%{time_namelookup} conn:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' \
https://example.com/api
curl -iL --resolve example.com:443:1.2.3.4 https://example.com/
curl -v -X POST -H 'Content-Type: application/json' -d '{"a":1}' http://127.0.0.1:8080/apinc -zv HOST PUERTO: -z sondea sin enviar datos y -v imprime el resultado; lo más rápido
-w3 fija 3 segundos de espera para que no se cuelgue (sin él puede tardar mucho)
telnet HOST PUERTO existe en sistemas viejos; si sale Connected, hay alcance
El /dev/tcp de bash no necesita instalar nada: echo > /dev/tcp/HOST/PUERTO
«refused» significa que el host responde pero nadie escucha; un tiempo agotado indica firewall o grupo de seguridad
Siempre acotar por capas: ss local, luego curl 127.0.0.1, luego nc desde la misma subred y al final desde fuera
nc -zv -w3 10.0.0.5 5432 echo > /dev/tcp/10.0.0.5/5432 && echo open || echo closed
ip a muestra todas las direcciones (el viejo ifconfig); -4 solo IPv4 y -6 solo IPv6
ip r imprime la tabla de rutas; la línea default es la puerta de enlace
ip neigh muestra la tabla ARP: si falla el tráfico en la misma subred, mirar si hay entrada
ip -s link da contadores de paquetes y errores (-s es statistics); errors/dropped distintos de 0 apuntan a la capa de enlace
show eth0 limita la salida a una interfaz y evita que las virtuales la inunden
add / del añaden o quitan direcciones de forma temporal; se pierden al reiniciar
Para persistir, según la distro: netplan o /etc/network/interfaces en Debian, nmcli en RHEL
ip -4 a ip r ip -s link show eth0
+short imprime solo la respuesta: cómodo en scripts
+A / +AAAA / +MX eligen el tipo de registro (A es el valor por defecto)
@8.8.8.8 consulta un servidor concreto, para comparar resolución local y pública
+trace recorre desde la raíz y señala qué delegación está mal
Cuando falla, mirar status: NXDOMAIN es que no existe el registro; SERVFAIL, que el servidor está mal
El TTL marca cuánto tarda en propagarse un cambio; el TTL restante en ANSWER SECTION indica cuánta caché queda
dig solo cubre DNS: si resuelve pero no conecta, el fallo está después, en la red o el puerto
dig +short example.com dig @8.8.8.8 example.com A dig +trace example.com
-i any escucha en todas las interfaces (o una concreta); -n evita resolver nombres y puertos: más rápido
-nn usa números para IP y puertos, más claro que -n al depurar
port 443 filtra por puerto; host 1.2.3.4 por host; se combinan con and / or
-c 100 para tras 100 paquetes en vez de inundar el terminal
-w file.pcap guarda para Wireshark, mucho mejor que leer en el terminal
-A muestra el contenido en ASCII y -X en hexadecimal más ASCII (solo útil con protocolos en claro)
tcp[tcpflags] & tcp-rst != 0 captura solo RST, para ver quién cerró primero
Requiere root; en producción añadir siempre -c y un filtro
tcpdump -i any -nn -c 50 'port 443 and host 1.2.3.4' tcpdump -i eth0 -nn -w /tmp/cap.pcap 'tcp[tcpflags] & tcp-rst != 0'
-connect HOST:443 inspecciona el certificado del otro lado en la capa TLS, sin HTTP
-servername HOST es obligatorio (SNI); si no, una IP con varios dominios entrega el certificado equivocado
-showcerts imprime la cadena completa y revela si falta un intermedio
-noout omite el certificado en bruto y muestra solo lo que se pida
-dates muestra notBefore / notAfter: el primer paso ante una caducidad
-subject muestra a quién se emitió; -ext subjectAltName lista los nombres cubiertos
-checkend 2592000 comprueba si caduca en 30 días (segundos); el código de salida sirve para monitorizar
</dev/null hace que s_client salga tras leer; si no, se queda esperando entrada
openssl s_client -connect www.icetools.cn:443 -servername www.icetools.cn </dev/null 2>/dev/null \ | openssl x509 -noout -dates -subject openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null | head -30 openssl s_client -connect example.com:443 -servername example.com -checkend 2592000 </dev/null >/dev/null \ && echo '30 天内不过期'
firewall-cmd --list-all muestra lo que permite la zona activa (familia RHEL)
--add-port=8080/tcp añade la regla; solo --permanent sobrevive al reinicio
--reload aplica las reglas permanentes: obligatorio tras añadirlas, o no entran en vigor
ufw status verbose muestra las reglas (Ubuntu)
iptables -L lista la tabla de filtro; -n mantiene los nombres numéricos y -v muestra contadores
--line-numbers muestra los índices, que es lo que se usa en iptables -D INPUT 3
-I INPUT 1 inserta al principio y -A añade al final: el orden decide el resultado
Si sigue sin funcionar tras abrir la regla, sospechar del grupo de seguridad del proveedor: está fuera de esta máquina
firewall-cmd --list-all firewall-cmd --add-port=8080/tcp --permanent && firewall-cmd --reload iptables -L INPUT -n -v --line-numbers
«El disco está lleno» tiene cuatro causas distintas: muchos archivos, uno enorme, inodos agotados o archivos borrados aún abiertos
Paso 1: df -h para ver qué punto de montaje está lleno; no reiniciar el servicio todavía
Paso 2: df -i para inodos. Con los inodos agotados aparece «no space left on device» aunque df -h muestre hueco
Paso 3: du -h --max-depth=1 para bajar nivel a nivel hasta el directorio grande (--max-depth son niveles)
Paso 4: si las cifras no cuadran, lsof +L1 encuentra archivos borrados aún abiertos; solo reiniciar o matar ese proceso los libera
La línea de awk suma la columna 7 (bytes) y la pasa a MB: más rápido que estimar a ojo
El crecimiento de logs suele verse en el paso 1: leer qué está inundando antes de borrar
df -h; df -i
du -h --max-depth=1 / | sort -h | tail
lsof +L1 2>/dev/null | awk '{s+=$7} END {print s/1024/1024" MB held"}'Salida en árbol por defecto: muestra cómo anidan LVM, particiones y puntos de montaje
-f muestra tipo de sistema de archivos, UUID y punto de montaje: mirar antes de tocar fstab
-p imprime rutas completas, para no confundir /dev/sda con /dev/sda1
-d oculta los hijos y muestra solo discos completos
-o elige columnas; NAME,SIZE,FSTYPE,MOUNTPOINT,UUID es el conjunto útil
Tras ampliar, si lsblk muestra el tamaño nuevo y df no, falta redimensionar el sistema de archivos
lsblk -f lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT,UUID
Ampliar un disco cloud son dos pasos: partición y luego sistema de archivos; si falta uno, df no cambia
growpart /dev/vda 1 amplía la partición (paquete cloud-utils-growpart); el segundo argumento es el número
xfs_growfs / amplía XFS (solo crece, no encoge)
resize2fs /dev/vda1 amplía ext4 (admite reducir, pero antes hay que umount y comprobar)
Con LVM hace falta además lvextend y después redimensionar el sistema de archivos
Confirmar con df -h que la capacidad cambió de verdad
Hacer una instantánea antes: un corte de luz durante el resize puede costar datos
growpart /dev/vda 1 xfs_growfs / # XFS resize2fs /dev/vda1 # ext4 df -h /
Opciones habituales de -o: ro solo lectura, remount, noatime para reducir escrituras
remount es la más usada: cambia opciones sin desmontar, sin cortar el servicio
Si umount dice device is busy, localizar quién lo tiene con lsof +D o fuser -vm
En fstab es mejor UUID que /dev/sdb: los nombres de dispositivo cambian
La cuarta columna suele ser defaults,noatime; las columnas 5 y 6 son orden de dump y fsck: 0 0 para discos de datos
Tras editar fstab, ejecutar siempre mount -a; una línea mala puede impedir el siguiente arranque
mount -a monta lo que falte según fstab y señala la línea con problemas
mount -o remount,rw / fuser -vm /mnt/data mount -a # validar la sintaxis de fstab
Cada archivo consume un inodo y el total se fija al formatear
Síntoma: df -h muestra espacio pero escribir falla con No space left on device
df -i muestra IUse%; cerca del 100% lo confirma
-xdev se queda en un sistema de archivos para que el recuento no salte a otros montajes
-type f cuenta solo archivos: los directorios también gastan inodos, pero la distribución de archivos es más informativa
El sed quita lo que sigue a la última barra y deja el directorio; sort | uniq -c cuenta archivos por directorio
Culpables habituales: archivos de sesión, temporales de subida, mail spool, cachés con cientos de miles de archivos
La solución de fondo es reformatear con un ratio -i mayor; a corto plazo, borrar archivos pequeños
El sort -rn final significa orden numérico inverso: -r invierte, -n compara como números
df -i find /var -xdev -type f | sed 's#/[^/]*$##' | sort | uniq -c | sort -rn | head
La mayoría de los cron que no corren son un problema de PATH o de redirección; un reloj mal ajustado rompe certificados, JWT y logs a la vez
crontab -l lista tus tareas; -e edita; -r borra todas sin confirmación
-u USUARIO opera sobre el crontab de otro usuario (ejecutar como root)
Los cinco campos son minuto hora día mes día de la semana: 0 3 * * * es a las 03:00 cada día
*/5 cada 5 minutos; 1-5 de lunes a viernes; 0,12 solo a las 00:00 y 12:00
El entorno de cron no es tu shell y el PATH es mínimo: usar siempre rutas absolutas
cron no lee ~/.bashrc: poner las variables arriba del crontab o hacer source en el script
Redirigir la salida: >> /var/log/x.log 2>&1, o los errores van al correo y nunca se ven
Para ver por qué no corrió: journalctl -u cron en Debian, /var/log/cron en RHEL
En los ejemplos, curl -sS calla pero avisa de errores (-s silencia, -S muestra) y -o /dev/null descarta el cuerpo
crontab -e crontab -l # copia de seguridad diaria a las 3:30, salida al log 30 3 * * * /srv/app/backup.sh >> /var/log/backup.log 2>&1 # comprobación de estado cada 5 minutos */5 * * * * /usr/bin/curl -sS -o /dev/null http://127.0.0.1:8080/health
Ventajas reales: los logs van a journalctl, se ve la próxima ejecución y lo perdido se recupera
OnCalendar usa sintaxis de calendario: *-*-* 03:30:00 a las 03:30 diario; OnUnitActiveSec=5min cada 5 minutos
Persistent=true ejecuta una vez tras el arranque lo perdido con la máquina apagada: cron no puede
Unit= nombra el servicio; si se omite, se usa el .service del mismo nombre
systemctl list-timers muestra todos los timers y su próxima ejecución
enable --now arranca al inicio y ejecuta ya; solo enable no dispara nada
Para probar, iniciar el .service, no el .timer: iniciar el timer no lanza la tarea
La última línea: journalctl -u elige la unidad, -n 30 toma 30 líneas y --no-pager evita el paginador
# /etc/systemd/system/backup.timer [Unit] Description=Daily backup [Timer] OnCalendar=*-*-* 03:30:00 Persistent=true [Install] WantedBy=timers.target # habilitar y comprobar systemctl list-timers systemctl enable --now backup.timer journalctl -u backup -n 30 --no-pager
date muestra hora y zona; +%F da 2026-10-29, +%T la hora, y +'%F %T' es la pareja habitual
-d '-1 hour' calcula hace una hora y -d 'next monday' también vale: toda ventana temporal usa -d
-s fija el reloj directamente; nunca en producción: desordena logs y rompe la validación de certificados
timedatectl muestra zona y estado de NTP, más completo que date
set-timezone Asia/Shanghai cambia la zona (el fallo típico local: sigue en UTC)
set-ntp true activa la sincronización; los contenedores heredan el reloj del anfitrión
Un reloj mal ajustado se nota: certificados «not yet valid», JWT caducados al instante, logs desalineados
date '+%F %T' date -d '-1 hour' '+%F %T' timedatectl timedatectl set-timezone Asia/Shanghai
chronyc tracking da desfase y estrato; la línea System time es literalmente cuánto se desvía
Un desfase de cientos de milisegundos exige revisar: fuente inalcanzable, UDP 123 bloqueado, deriva de la VM
chronyc sources -v lista cada fuente y con -v el detalle (retardo, jitter, si está seleccionada)
Un ^* al inicio marca la fuente en uso; ^? significa inalcanzable
chronyc makestep salta el reloj de inmediato: manual solo con desfase grande, y queda registrado
ntpq -p es el equivalente en sistemas viejos con ntpd y -p imprime la lista de pares
En servidores cloud conviene el NTP interno del proveedor; el público suele limitar o rechazar
chronyc tracking chronyc sources -v chronyc makestep ntpq -p # sistemas antiguos (ntpd)
El orden manda más que la sintaxis: acotar el tiempo, filtrar por palabra clave y leer el contexto
-u UNIDAD filtra a un servicio
-f sigue en vivo; --since '10 min ago' o --since '2026-10-29 09:00' acotan la ventana
-p err muestra error y superiores; las prioridades van de emerg a debug
-n 100 las últimas 100 líneas; -r invierte (lo más reciente arriba)
--no-pager evita el paginador, útil al redirigir o encadenar tuberías
-o short-iso incluye el desfase de zona: imprescindible entre regiones
--disk-usage informa cuánto ocupa el journal; --vacuum-time=7d conserva 7 días
--vacuum-size=500M recorta por volumen: elegir una de las dos
journalctl -u nginx -p err --since '30 min ago' --no-pager journalctl -u app -f -o short-iso journalctl --disk-usage
-f sigue el descriptor: tras rotar el log sigue leyendo el archivo viejo y parece congelado
-F sigue por nombre y reabre tras la rotación: la opción para vigilar un rato
-n 100 muestra primero las últimas 100 líneas; -n0 no muestra historial, solo lo nuevo
-Fn0 es -F más -n0, la pareja habitual para un flujo en vivo
--pid=PID deja de seguir cuando ese proceso termina
grep --line-buffered hace que grep vuelque por línea; sin él la tubería se queda sin mostrar nada
Juntos: tail -Fn0 app.log | grep --line-buffered -i error
Varios archivos: tail -F a.log b.log añade separadores ==>, no vigilar solo uno
tail -F /var/log/app/error.log tail -Fn0 /var/log/nginx/access.log | grep --line-buffered -i 500
OOM, errores de IO de disco, caídas de enlace y fallos de controladores aparecen aquí
-T imprime fechas legibles; -w sigue en vivo
-l err,warn filtra por nivel (-l toma una lista separada por comas)
El búfer circular se sobrescribe: capturar pronto; journalctl -k es la versión persistente
«Out of memory: Killed process» significa que lo mató el OOM killer, no un fallo propio
Con grep -iE: -i ignora mayúsculas y -E activa la regex extendida (así funciona |, ver grep)
dmesg -T -l err,warn | tail -30 dmesg -T | grep -iE 'oom|killed process'
Confirmar con dmesg -T | grep -i -A2 'killed process': -A2 trae las dos líneas siguientes, donde está el consumo de la víctima
journalctl -k trae el mismo evento y el estado de memoria del momento
status=9/KILL o signal=KILL en systemctl status casi siempre es el OOM killer
--no-pager evita que el paginador tome la salida: necesario en scripts y capturas
No es un crash: el log de la aplicación corta en seco, sin traza de excepción
Buscar la causa: free -h para la memoria disponible y luego ordenar por RSS para ver quién crece
En contenedores mirar además el límite cgroup en /sys/fs/cgroup/memory.max
dmesg -T | grep -i -A2 'killed process' systemctl status app --no-pager | head -20 free -h
Las configuraciones van en /etc/logrotate.d/, un archivo por servicio
daily / weekly marcan el periodo; rotate 7 conserva 7 copias
compress comprime los antiguos; delaycompress deja el más nuevo sin comprimir
copytruncate copia y luego trunca, para programas que no pueden reabrir el archivo
postrotate señala al proceso para que reabra el archivo: más limpio que copytruncate
-d es un ensayo: ejecutarlo tras cada cambio para ver el plan
-f fuerza una rotación ahora para verificar la configuración sin esperar al periodo
missingok silencia «no such file» y notifempty omite los vacíos: casi cualquier configuración debería llevarlos
# /etc/logrotate.d/app
/var/log/app/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
postrotate
systemctl reload app > /dev/null 2>&1 || true
endscript
}
# simular primero y luego forzar una ejecución para verificar
logrotate -d /etc/logrotate.d/app
logrotate -f /etc/logrotate.d/appCasi todo servicio que no arranca es una de tres cosas: ruta mal, usuario sin permisos o dependencia ausente; status lo dice
start / stop / restart / reload: reload relee la configuración sin reiniciar, y solo si el programa lo soporta
status muestra las últimas líneas del journal y el código de salida; --no-pager evita el paginador
enable activa al arranque; --now además lo inicia ahora
disable quita el arranque automático pero no lo detiene: hace falta stop
list-units --failed muestra lo roto; --all incluye las unidades inactivas
Los códigos de is-active / is-enabled sirven directamente en scripts
cat imprime la unidad tal como la lee systemd (con los drop-in): revisarlo tras editar
systemctl status app --no-pager systemctl enable --now app systemctl list-units --failed
Colocarla en /etc/systemd/system/NOMBRE.service y ejecutar daemon-reload después
ExecStart exige ruta absoluta y el programa no debe hacer fork por su cuenta (si lo hace, Type=forking)
Restart=on-failure con RestartSec=5 evita tormentas de reinicios
User / Group deciden permisos; al salir de root hay que ajustar también el home y los propietarios
EnvironmentFile deja las variables en un archivo aparte en vez de incrustarlas en ExecStart
WorkingDirectory afecta a las rutas relativas; sin él muchos programas no encuentran su configuración
StandardOutput=journal envía los logs a journalctl
After solo ordena y no garantiza nada; para la red añadir Wants=network-online.target
# /etc/systemd/system/app.service [Unit] Description=App After=network-online.target Wants=network-online.target [Service] Type=simple User=deploy WorkingDirectory=/srv/app EnvironmentFile=/etc/app/env ExecStart=/srv/app/bin/app Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
1. systemctl status: mirar Active y las últimas líneas de error; resuelve la mayoría
2. journalctl -u SERVICIO -n 50 --no-pager para la traza completa (-n es el número de líneas)
3. Confirmar que la ruta de ExecStart existe y es ejecutable: ls -l sobre ella
4. Ejecutarla a mano con sudo -u USUARIO_SERVICIO: el error real suele aparecer al instante
5. Revisar políticas: ausearch -m avc -ts recent (-m elige el tipo avc y -ts el inicio)
Los rechazos de AppArmor van a dmesg; en contenedores, leer además docker logs
status=203/EXEC indica ruta o permisos; status=1 es el programa saliendo con código 1
systemctl status app --no-pager journalctl -u app -n 50 --no-pager sudo -u deploy /srv/app/bin/app # ejecutar a mano para ver el error real ausearch -m avc -ts recent # registro de denegaciones de SELinux
Editar un .service: daemon-reload obligatorio, o systemd sigue con la definición vieja
Cambiar solo valores del EnvironmentFile: no hace falta reload, pero sí restart para aplicarlos
Unidad nueva: hay que hacer reload antes de poder enable
Un servicio en marcha no adopta la configuración nueva con reload: hay que restart
Síntoma típico de olvidar el reload: se cambia ExecStart y nada cambia, status sigue con el comando viejo
cat muestra el archivo tal como lo ve systemd, con los drop-in aplicados
systemctl daemon-reload && systemctl restart app systemctl cat app # comprobar cómo ve systemd este archivo
After solo ordena el arranque, no garantiza que el otro lo logre
Requires es dependencia dura: si el otro falla, este también
Wants es blanda: este arranca igual; el valor por defecto sensato
Para red, network.target solo indica pila inicializada; las direcciones pueden no estar listas
After=network-online.target solo espera de verdad junto a Wants=network-online.target
Un «fallo de resolución de nombres» justo al arrancar suele ser esto
Verificar con systemctl list-dependencies SERVICIO para ver el árbol
[Unit] Wants=network-online.target After=network-online.target # comprobar systemctl list-dependencies app
Mirar el origen antes de instalar y qué se lleva por delante antes de borrar, sobre todo en producción
apt update solo refresca índices y no actualiza nada: la pareja que más se invierte
apt upgrade actualiza lo instalado; full-upgrade puede quitar paquetes para resolver dependencias
install -y evita la confirmación y va bien en scripts; sin -y apt espera entrada
apt purge borra también la configuración; remove a secas la deja
apt list --installed lista lo presente; apt list --upgradable filtra lo actualizable
apt-cache show PAQUETE da versión y descripción
apt-mark hold fija una versión para que las actualizaciones masivas la salten
apt update && apt install -y nginx apt list --upgradable apt purge nginx-common
dnf sustituye a yum; en sistemas nuevos yum suele ser un enlace simbólico
install / remove / update no coinciden con apt: no mezclarlos
install -y omite las preguntas, igual que en apt
provides busca un paquete por archivo o comando
history lista las transacciones y history undo last revierte la anterior: salvavidas tras un upgrade fallido
--enablerepo habilita un repositorio para una ejecución; --nogpgcheck solo cuando no haya otra
Los módulos (dnf module list) importan cuando conviven varias versiones de un paquete
dnf install -y nginx dnf provides /usr/sbin/ss dnf history undo last
RHEL: rpm -qf /usr/bin/ss: -q entra en modo consulta y -f busca por archivo
Debian: dpkg -S /usr/bin/ss; si no aparece, apt-file update y luego apt-file search
Sin ruta completa, buscar por nombre: dnf provides '*/ss' o apt-file search bin/ss
dpkg -L PAQUETE lista todos los archivos instalados: útil antes de tocar configuraciones
rpm -qc PAQUETE lista solo los archivos de configuración (-qc es -q más -c de config)
Cuando falta un comando, confirmar que el paquete está instalado antes de culpar al PATH
rpm -qf /usr/bin/ss dpkg -S /usr/bin/ss dpkg -L nginx-common | head -20
apt clean borra toda la caché de .deb; apt autoclean solo lo caducado
dnf clean all limpia caché y metadatos
apt autoremove quita paquetes instalados como dependencia que ya nadie usa; --purge borra también su configuración
A autoremove hay que añadirle -y, o se detiene en la confirmación
Antes de limpiar, du -sh para confirmar que el espacio es real (-s total, -h legible)
No limpiar tras apt install en una imagen Docker desperdicia decenas de MB
du -sh /var/cache/apt /var/cache/dnf 2>/dev/null apt clean && apt autoremove --purge -y dnf clean all
Nueve de cada diez problemas de contenedores son un desajuste entre la configuración que crees y la que corre: alinear los hechos con inspect primero
-a incluye los contenedores detenidos: listar solo los activos oculta el que acaba de caer
-q imprime solo los ID, cómodo para docker rm o docker logs
-s muestra el tamaño de la capa escribible, para cazar el contenedor que llenó el disco
--format define columnas; '{{.Names}} {{.Status}}' se lee mejor que la tabla por defecto
--filter acota: status=exited para los muertos, name=web por nombre
--no-trunc no recorta ID largos ni comandos completos
-n 5 muestra los 5 creados más recientemente (abreviatura de --last)
Up 3 seconds (restarting) en Status es un bucle de reinicio: mirar docker logs --tail
# todos los contenedores (incluidos los detenidos)
docker ps -a --format '{{.Names}} {{.Status}}'
# encontrar los detenidos o que se reinician en bucle
docker ps -a --filter status=exited -n 5
# quién está llenando el disco
docker ps -s --format '{{.Names}} {{.Size}}'-f sigue el flujo, como tail -f
--tail 200 solo las últimas 200 líneas: un contenedor antiguo tiene decenas de miles
-t pone marca de tiempo en cada línea, clave al cruzar con los logs del host
--since 10m solo los últimos 10 minutos; también acepta horas absolutas como 2026-10-29T10:00:00
--until se combina con --since para recortar una ventana
La -i de grep significa ignorar mayúsculas: atrapa error / ERROR / Error de una vez
Los logs viven en /var/lib/docker/containers/<ID>/<ID>-json.log y desaparecen con el contenedor:
enviar los de servicios importantes a journald o a un recolector, no dejarlos solo en docker
docker logs -f --tail 200 -t myapp docker logs --since 10m myapp | grep -i error
-it terminal interactivo; sin -t no hay prompt y sin -i no se puede escribir: pasar ambos
-u root entra como root, para imágenes cuyo usuario no puede leer la configuración
-w /app fija el directorio de trabajo y evita un cd posterior
-e KEY=val inyecta una variable temporal para comprobar si faltaba pasarla
-- impide que docker interprete lo siguiente: obligatorio si el comando empieza por -
-c en bash ejecuta el resto como cadena: bash -c 'a && b' hace varios pasos
-l en ls da formato largo con permisos y propietario
Si exec falla, comprobar con docker ps -a que el contenedor corre: en un Exited falla siempre,
y el problema real es que el proceso principal ya murió
Sin bash en la imagen, probar sh: las basadas en alpine suelen tener solo sh
Las imágenes distroless no tienen shell: usar docker cp o un contenedor de depuración temporal
docker exec -it -u root myapp sh docker exec myapp env | grep -i database docker exec -it myapp bash -c 'ls -l /app'
--format evalúa una plantilla Go; -f es la abreviatura
Rutas útiles: .State.Status, .State.ExitCode,
.NetworkSettings.Networks para la IP, .Mounts para montajes, .Config.Env para variables,
.HostConfig.RestartPolicy para la política de reinicio
{{json .}} emite JSON en bruto, más fácil con jq
--type image inspecciona una imagen en vez de un contenedor
Los valores de compose pueden quedar anulados por -e de la línea de comandos
.Config.Env suele traer contraseñas y tokens: filtrarlo antes de pegar en cualquier sitio
docker inspect -f '{{.State.Status}} exit={{.State.ExitCode}}' myapp
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myapp
docker inspect --format '{{json .Mounts}}' myapp | jq--no-stream toma una instantánea y sale: ideal en scripts y registros periódicos
--format define columnas, cómodo para grep o para cargar en base de datos
-a incluye contenedores detenidos (muestran 0, pero aparecen)
MEM LIMIT es el tope del contenedor; un MEM % cerca de 100 anuncia OOM
CPU % se escala por núcleos: superar 100% en un host multinúcleo es normal
Si el contenedor fue OOMKilled, stats no muestra nada: mirar ExitCode 137 en docker inspect
y las líneas oom-kill de dmesg
docker stats --no-stream --format '{{.Name}} {{.CPUPerc}} {{.MemUsage}}'
docker stats --no-stream -a-t etiqueta la imagen; mejor nombre:versión que solo latest
-f apunta a un Dockerfile fuera del directorio de construcción
--no-cache ignora la caché por completo: lo primero cuando «cambié el código y la imagen no»
--build-arg pasa un argumento; solo funciona si el Dockerfile lo declara con ARG
--target se detiene en una etapa concreta, útil para depurar capas intermedias
--progress=plain imprime el log completo; el modo TTY colapsa la salida paralela y oculta errores
El . final es el contexto, no la ruta del Dockerfile: uno grande frena la construcción
docker build -t myapp:1.2.3 . docker build -f docker/Dockerfile.prod --build-arg NODE_ENV=prod --progress=plain -t myapp:prod .
-f elige el archivo compose; con dos -f se apilan entornos y gana el último
-p fija el nombre de proyecto: por defecto toma el del directorio, y con varios stacks hay que indicarlo
-d ejecuta en segundo plano; sin él se adjunta y Ctrl+C lo detiene
--build reconstruye las imágenes antes de arrancar
up crea y arranca; down detiene y borra contenedores y redes
logs -f --tail 100 sigue los logs; añadir un servicio limita a ese
config valida y muestra la configuración final fusionada: más seguro que ir directo a up
compose v2 es docker compose (con espacio) y v1 es docker-compose (con guion): las opciones difieren algo
docker compose -f docker-compose.yml -f docker-compose.prod.yml -p shop up -d --build docker compose -p shop logs -f --tail 100 api docker compose -p shop config
system df resume el uso por imágenes / contenedores / volúmenes / caché de construcción; -v lo desglosa
prune elimina objetos sin usar; por defecto solo imágenes colgantes (<none>), con -a todas
--volumes limpia también volúmenes: puede haber una base de datos dentro y no se recupera
--filter until=24h borra solo lo anterior a 24 horas y evita tocar lo que está en uso
Orden recomendado: system df -v para ver quién ocupa y luego un prune dirigido
Los logs de contenedor también llenan el disco: /var/lib/docker/containers/*/*-json.log;
la solución de fondo es configurar log-opts con max-size y max-file en daemon.json
docker system df -v docker image prune -a --filter until=24h docker system prune --volumes
El orden de diagnóstico en K8s es casi fijo: estado, eventos, logs y shell. Saltarse un paso es adivinar
-A todos los espacios de nombres: añadirlo cuando un recurso «no existe»; casi siempre es el equivocado
-n elige el espacio de nombres; sin él se usa el del contexto
-o wide añade columnas de IP y nodo, imprescindibles en problemas de red y planificación
-o yaml imprime la definición completa: exportar antes de editar
--show-labels muestra etiquetas; cuando un Service no encuentra sus Pods casi siempre es un desajuste
-l app=api filtra por etiqueta, más fiable que buscar nombres
--sort-by=.status.startTime ordena por hora de inicio, lo más nuevo al final
Hay nombres cortos: pod/po, svc, deploy, rs, no, cm, secret
kubectl get pod -A -o wide kubectl get pod -n shop -l app=api --show-labels kubectl get pod -A --sort-by=.status.startTime
El bloque Events del final es la parte más informativa del diagnóstico:
ahí aparecen las causas de ImagePullBackOff, FailedScheduling, Unhealthy y OOMKilled
Omitir -n y acertar mal el espacio de nombres deja un NotFound sin pista alguna
Los Events se conservan una hora por defecto: capturarlos, no contar con verlos luego
describe node termina con Allocated resources, el porcentaje de request / limit asignado:
con un Pod en Pending, mirar si los request agotaron el nodo (no si la CPU está ocupada)
describe solo cubre configuración y eventos, nunca logs de aplicación: eso es kubectl logs
kubectl describe pod -n shop api-7d9f-abc12 kubectl describe node k8s-node-2 | tail -30
-f sigue la salida
-n elige el espacio de nombres; sin él se usa el del contexto
--tail 200 las últimas 200 líneas; un Pod antiguo inunda el terminal sin esto
-c elige el contenedor: obligatorio con sidecar, o aparece ambiguous con la lista de contenedores
--previous lee el contenedor anterior: CrashLoopBackOff reinicia sin parar,
así que el log actual solo muestra el «murió al arrancar» y la causa real está en el anterior
--since 10m solo los últimos 10 minutos
--timestamps pone marca de tiempo en cada línea
Los recursos aceptan la forma tipo/nombre como deploy/api para leer los Pods de un Deployment
kubectl logs -f --tail 200 -n shop deploy/api kubectl logs -n shop api-7d9f-abc12 --previous kubectl logs -n shop api-7d9f-abc12 -c istio-proxy --since 10m
-it terminal interactivo: las dos juntas
-n elige el espacio de nombres
-c elige el contenedor; obligatorio en Pods con varios
-- impide que kubectl interprete el resto: obligatorio si el comando empieza por -
cp toma origen y destino, con el lado del Pod como pod:ruta
cp depende de tar dentro del contenedor: falla sin más en imágenes distroless
kubectl debug inyecta un contenedor de depuración que comparte el espacio de nombres de red;
--image elige la imagen (busybox basta) y --target nombra el contenedor a depurar:
es la vía correcta cuando la imagen de producción no tiene shell
kubectl exec -it -n shop api-7d9f-abc12 -- sh kubectl cp -n shop api-7d9f-abc12:/app/logs/app.log ./app.log kubectl debug -it -n shop api-7d9f-abc12 --image=busybox --target=api
Misma fuente que los Events de describe, pero legible entre recursos: útil cuando no se sabe qué objeto falla
-n elige el espacio de nombres
--sort-by=.lastTimestamp ordena por hora, lo más nuevo al final
--field-selector type=Warning muestra solo avisos: un orden de magnitud menos de ruido
--field-selector involvedObject.name=api-7d9f-abc12 acota a un objeto
-A todos los espacios; los problemas de clúster (nodo NotReady, desalojos) solo aparecen con él
FailedScheduling trae las palabras del planificador, por ejemplo
insufficient cpu o node(s) had taint: dice directamente por qué no se coloca
sin adivinar entre falta de recursos o taints
Los eventos viven cerca de una hora: copiarlos a tiempo durante un bucle de reinicio
kubectl get events -A --sort-by=.lastTimestamp | tail -20 kubectl get events -n shop --field-selector type=Warning kubectl get events -n shop --field-selector involvedObject.name=api-7d9f-abc12
-f apunta a un manifiesto o directorio; -f - lee de la entrada estándar
--dry-run=client no envía nada, solo imprime el YAML final: sirve para generar plantillas
--dry-run=server valida en el servidor (con webhooks de admisión), mucho más estricto
-o yaml es lo que hace útil la salida del dry-run
rollout status espera a que termine el despliegue y dice dónde se atascó
rollout undo vuelve a la revisión anterior; --to-revision=3 elige una, y history las lista
rollout restart reinicia los Pods sin cambiar configuración: tras editar un ConfigMap o Secret
-n elige el espacio de nombres
apply es declarativo e idempotente; create es imperativo y falla con AlreadyExists la segunda vez
kubectl apply -f deploy/api.yaml --dry-run=client -o yaml > api.gen.yaml kubectl rollout status deploy/api -n shop kubectl rollout undo deploy/api -n shop --to-revision=3 kubectl rollout restart deploy/api -n shop
El formato es recurso/nombre puerto_local:puerto_remoto, sin NodePort ni Ingress
-n elige el espacio de nombres
--address 0.0.0.0 permite que otras máquinas de la subred entren; por defecto solo escucha en 127.0.0.1
El tráfico pasa por un túnel del kube-apiserver, sin tocar el Service:
por eso «¿la aplicación está sana?» y «¿una NetworkPolicy me bloquea?» son preguntas distintas
Al cerrar el terminal se corta: para sesiones largas, dejarlo en tmux o screen
Orden de diagnóstico: si port-forward funciona, la aplicación está bien y falla Ingress / Service / NetworkPolicy;
si falla, el Pod o el puerto están mal
kubectl port-forward -n shop svc/api 8080:80 kubectl port-forward -n shop --address 0.0.0.0 svc/api 8080:80 kubectl port-forward -n shop pod/api-7d9f-abc12 5432:5432
top node y top pod necesitan metrics-server; sin él aparece Metrics API not available
--sort-by=cpu o --sort-by=memory ordena la lista y saca a los grandes consumidores
--containers desglosa un Pod por contenedor y revela qué sidecar consume memoria
-A todos los espacios de nombres
Allocated resources en describe node es la proporción de request / limit asignada:
mirarlo primero cuando falla la planificación: request exagerados agotan el nodo en papel
cordon marca el nodo como no programable sin tocar los Pods en marcha
drain desaloja los Pods para mantenimiento; --ignore-daemonsets omite los DaemonSet
(que nunca deberían desalojarse) y --delete-emptydir-data permite borrar Pods con emptyDir
Al terminar, uncordon restablece la programación
kubectl top node kubectl top pod -A --sort-by=memory --containers kubectl drain k8s-node-2 --ignore-daemonsets --delete-emptydir-data
top dice qué proceso quema CPU, no en qué está esperando. Estas herramientas responden la segunda pregunta
-p se adjunta a un proceso en marcha; también se puede prefijar un comando
-f sigue a los hijos creados con fork: sin él solo se ve al padre esperando
-T imprime la duración de cada llamada (<0.000012>): así se localiza el paso lento
-tt marcas de tiempo en microsegundos
-s 200 muestra hasta 200 caracteres por cadena; los 32 por defecto cortan rutas clave
-o out.txt escribe a un archivo: decenas de miles de líneas no se analizan en el terminal
-c resume al salir recuentos y tiempo total por llamada al sistema:
ejecutarlo primero para ver la clase cara, y luego -T para una llamada lenta concreta
Adjuntar con -p y no ver nada significa IO ininterrumpible o un lock del núcleo:
mirar después /proc/PID/stack y /proc/PID/wchan
Cuidado en producción: -f sobre todas las llamadas puede ralentizar el proceso varias veces
strace -p 1234 -T -tt -s 200 strace -f -p 1234 -o /tmp/st.txt strace -c -p 1234
-e trace= es la opción más útil de strace, sin discusión
-e trace=file solo llamadas de archivo (openat, stat, unlink): para «no encuentro el archivo»
-e trace=network solo red (connect, sendto, recvfrom): para «no conecta» o tiempos agotados
-e trace=openat,connect separadas por comas, para unas pocas concretas
-e trace=desc solo operaciones de descriptor, para ver qué fd bloquea
-e signal=SIGSEGV sigue solo una señal
El filtrado reduce decenas de miles de líneas a docenas: el orden correcto es -c para ver la clase cara
y luego -e trace= para seguir solo esa clase
openat con ENOENT significa ruta ausente, EACCES permisos insuficientes y EAGAIN sin datos;
los tres exigen arreglos distintos: no tomar todo error como problema de permisos
El resto coincide con strace -p: -f sigue hijos, -p apunta a un proceso, -T da el tiempo por llamada
strace -f -e trace=file -p 1234 -T strace -f -e trace=network -p 1234 -tt strace -o net.txt -e trace=openat,connect -f ./app
perf top es un perfil de funciones calientes en vivo: no hace falta PID, cubre todo el equipo
-p limita a un proceso
-F 99 muestrea a 99 Hz: evitar los 100 Hz redondos para no crear falsos puntos calientes
-g muestra grafo de llamadas; aquí la vista es limitada, las cadenas reales necesitan record
Un nombre unknown indica falta de debuginfo o símbolos eliminados:
no está roto perf, ese binario se distribuyó sin tabla de símbolos
Requiere root o relajar kernel.perf_event_paranoid; en contenedores suele hacer falta --privileged o SYS_ADMIN
Diferencia con top: top responde «qué proceso» y perf top «qué función»
perf top perf top -p 1234 -F 99 perf top -g
record captura muestras, report las navega y script vuelca texto: el trío
-F 99 toma 99 muestras por segundo
-g registra cadenas de llamadas, imprescindible para flame graphs
-p apunta a un proceso; -a muestrea todos los núcleos del sistema
--call-graph dwarf desenrolla con información DWARF, más preciso que el fp por defecto,
a costa de un orden de magnitud más de datos; obligatorio si el binario usa -fomit-frame-pointer
-o fija el archivo de salida; por defecto perf.data en el directorio actual
-i indica a report o script qué archivo leer
-- prefija un comando para que perf lo lance y muestree desde el arranque
Los flame graphs necesitan la salida plegada de script, que se pasa a stackcollapse de FlameGraph
En contenedores perf_event_paranoid suele ser 2, lo que oculta símbolos del núcleo:
con sysctl kernel.perf_event_paranoid=-1 se ve todo
perf record -F 99 -g -p 1234 -o /tmp/perf.data perf report -i /tmp/perf.data perf script -i /tmp/perf.data > /tmp/perf.txt
pidstat 1 imprime cada segundo y sigue refrescando; un segundo número limita las iteraciones
-p ALL muestra todos los procesos; -p 1234 uno concreto
-d IO de disco (kB_rd/s lectura, kB_wr/s escritura, iodelay en milisegundos)
-r memoria (fallos de página, RSS)
-u uso de CPU (el modo por defecto)
-t desglose por hilo, para ver qué hilo consume la CPU: esencial en programas multihilo
-w cambios de contexto: cswch/s voluntarios (esperando recurso), nvcswch/s involuntarios
Forma parte de sysstat, así que las instalaciones mínimas deben añadirlo
Mejor que top para muestrear a intervalo fijo en un archivo y luego promediar o graficar
pidstat -p 1234 1 10 pidstat -d -p ALL 1 5 pidstat -t -p 1234 1
iostat -x 1 estadísticas extendidas, refrescadas cada segundo
-x imprime las métricas extendidas; sin él se obtiene otra tabla mucho más escueta
%util cerca del 100% indica disco saturado; await es la espera media por IO en milisegundos
rkB/s y wkB/s son el caudal: await alto con caudal bajo apunta a un disco lento,
await alto con caudal alto es carga real; cada caso pide una ampliación distinta
vmstat 1 da una línea por segundo: r es la cola de ejecución (por encima de los núcleos, falta CPU),
b los procesos bloqueados y si / so el intercambio (distinto de 0 indica presión de memoria)
wa es la proporción de CPU esperando IO: alto señala el disco, no la CPU
-d en vmstat da estadísticas de disco y muestra solo esas columnas
La primera línea es el promedio desde el arranque; los valores reales empiezan en la segunda
iostat -x 1 5 vmstat 1 5 vmstat -d 1
Con un proceso colgado y strace sin salida (estado ininterrumpible), hace falta la pila de usuario
gdb -p pausa el proceso: hay que hacer detach antes de salir o queda congelado
-ex ejecuta una orden de gdb y sigue; encadenando varias se adjunta, vuelca pilas y desengancha
thread apply all bt vuelca la pila completa de cada hilo; con bt basta en programas de un hilo
gcore escribe un core para analizar después, más seguro que depurar en vivo
-o fija la ruta del core (sin sufijo .PID)
No usar gdb con una JVM: no lee las pilas de Java, usar jstack
--call-graph de perf toma muestras puntuales; un cuelgue exige la pila completa
gdb -p 1234 -ex 'thread apply all bt' -ex detach gcore -o /tmp/core.1234 1234
En los servidores suele haber solo vi, así que editar configuración pasa por él. Con veinte teclas basta
vim +120 ARCHIVO salta a la línea 120: la forma más rápida de seguir un número de línea de un log
-u NONE no carga configuración ni plugins, para descartar un vimrc roto
-w /tmp/keys.log registra cada pulsación, para saber qué se tecleó
:w guarda; :w! fuerza la escritura (archivo de solo lectura o sin permisos)
:q sale y se niega si hay cambios sin guardar; :q! descarta y sale
:wq o :x guardan y salen; :x no reescribe si no hubo cambios
:e! descarta los cambios y recarga, para empezar de nuevo tras romperlo
:saveas /tmp/a.conf guarda como: el paso estándar de copia antes de editar
Si editaste un archivo de solo lectura por error, :q! es la salida rápida
vim +120 /etc/nginx/nginx.conf vim -u NONE /etc/nginx/nginx.conf :w 保存 :q! salir descartando los cambios :wq 保存退出 :x guardar solo si hay cambios :e! 重新加载 :saveas /tmp/a.conf guardar como
gg primera línea; G última; 42G salta a la línea 42
0 inicio de línea; ^ primer carácter no blanco (útil al alinear sangría); $ final de línea
w siguiente palabra; b palabra anterior; e final de la palabra actual
Ctrl+f página abajo; Ctrl+b página arriba; Ctrl+d media abajo; Ctrl+u media arriba
% salta al paréntesis o llave correspondiente: rapidísimo para hallar el cierre de un bloque
'' vuelve a la posición anterior (dos comillas simples); Enter también sirve
En archivos grandes, evitar las flechas: hjkl con los movimientos de arriba es mucho más rápido
:set nu antes de editar, para que los números de línea de los errores signifiquen algo
gg 文件首行 G última línea del archivo 42G 第 42 行 0 inicio de línea ^ 行首首个非空白 $ final de línea w / b / e 下词首 / 上词首 / 词尾 Ctrl+f 下一页 Ctrl+b página anterior % 跳到匹配的括号 '' volver a la última posición
dd borra la línea entera: en realidad corta, y p la pega de vuelta
yy copia la línea; 5yy copia cinco
p pega debajo de la línea actual; P pega encima
u deshace; Ctrl+r rehace (cuando se deshizo de más)
x borra un carácter; r sustituye el carácter bajo el cursor sin entrar en inserción
cc reescribe la línea entera, vaciando el contenido y manteniendo la sangría
d$ borra hasta el final; d0 hasta el inicio; dw una palabra
. repite el último cambio: la tecla más valiosa de Vim
i inserta antes del cursor; a después; o abre línea debajo; O abre línea encima
Esc vuelve al modo normal: olvidarlo es cómo se teclea una línea de basura
dd / yy / p borrar línea / copiar línea / pegar 5yy copiar 5 líneas u / Ctrl+r deshacer / rehacer x / r borrar carácter / reemplazar un carácter cc reescribir la línea completa (conservando la indentación) . repetir el último cambio (la tecla más valiosa) i / a / o / O insertar antes / insertar después / nueva línea debajo / nueva línea arriba
/pattern busca hacia abajo; ?pattern hacia arriba
n siguiente coincidencia; N la anterior
* busca la palabra bajo el cursor sin teclearla; # busca hacia atrás
:set hlsearch resalta todas las coincidencias; :noh quita el resaltado
:%s/old/new/g reemplaza en todo el archivo; g cambia todas por línea, sin g solo la primera
:%s/old/new/gc pregunta cada vez: añadir siempre c en configuración
:10,20s/old/new/g limita a las líneas 10 a 20
:.,$s/old/new/g desde la línea actual hasta el final
El separador es intercambiable: :s#/usr/local/old#/usr/local/new#g evita escapar cada barra
Contar coincidencias con /pattern antes de reemplazar, para saber qué se va a cambiar
:%s/old/new/g reemplazo global (todas las coincidencias de cada línea) :%s/old/new/gc confirmar cada reemplazo (al tocar configuración, añadir siempre c) :10,20s/old/new/g reemplazar solo las líneas 10-20 :.,$s/old/new/g de la línea actual al final del archivo :s#/usr/local/old#/usr/local/new#g cambiar el separador para no escapar las rutas :noh quitar el resaltado de búsqueda
Ctrl+v inicia selección por bloque: abarcar una columna sirve para comentar o alinear en masa
Con el bloque activo, I inserta al inicio de cada línea y solo aplica al pulsar Esc
A añade al final de cada línea seleccionada
c sustituye el bloque completo y d lo borra
:g/pattern/d borra todas las líneas que coinciden
:g/pattern/s/old/new/g sustituye solo en las líneas que coinciden: más seguro que un reemplazo global
:sort ordena las líneas seleccionadas; :sort u ordena y quita duplicados
:normal @a ejecuta el macro del registro a una vez por línea: para trabajo repetitivo
:set nu primero, para no confundir el rango en ediciones por bloque
Ctrl+v I Esc tras seleccionar bloque, insertar al inicio de cada línea (comentario masivo) Ctrl+v A Esc tras seleccionar bloque, añadir al final de cada línea :g/^#/d borrar todas las líneas que empiezan por # :g/ERROR/s/old/new/g reemplazar solo en las líneas que contienen ERROR :sort u ordenar y deduplicar las líneas seleccionadas
Al pegar desde un navegador o Windows la sangría se desplaza: :set paste antes y :set nopaste después
(o :set pastetoggle=<F2> para alternar con F2)
Un archivo editado en Windows muestra ^M al final: :set ff=unix y luego :w
:set ff a solas informa si está en dos o unix
Texto mal codificado: :set fileencoding=utf-8 y guardar; para solo leerlo bien sin tocarlo,
usar :e ++enc=gbk para reinterpretarlo como GBK
:w !sudo tee % resuelve «olvidé sudo y ahora no puedo guardar»:
escribe el búfer con sudo en el nombre actual, más rápido que reabrir y repetir los cambios
Todo esto es por sesión: para que persista, ponerlo en ~/.vimrc
:set paste desactivar la autoindentación antes de pegar :set nopaste restaurar después de pegar :set ff=unix eliminar los ^M de Windows :set fileencoding=utf-8 corregir la codificación y guardar :w !sudo tee % forzar la escritura cuando se olvidó sudo
«El servidor va lento» no es un problema sino cuatro: CPU, memoria, IO y red. Un comando dice cuál
uptime muestra los promedios de carga de 1/5/15 minutos; por encima de los núcleos hay cola
Carga alta con CPU ociosa (%id alto en top) apunta a espera de IO: mirar la columna wa
En free -h leer la columna available, no la de free
Revisar df -h (capacidad) y df -i (inodos) juntos: un disco lleno provoca fallos inexplicables
vmstat 1 5 muestrea cada segundo cinco veces: r (cola), b (bloqueados), wa (espera de IO)
Identificar la capa antes de actuar es mucho más eficaz que reiniciar el servicio
uptime free -h df -h; df -i vmstat 1 5
Mirar la columna wa de vmstat: sostenida por encima de 10 significa que el IO arrastra
Luego b (procesos bloqueados): por encima de los núcleos hay muchos esperando recursos
Localizar el culpable con pidstat -d 1 5 (-d solo IO, cada segundo, cinco veces)
En ps -eo, -e abarca todos los procesos y -o elige columnas; state muestra D
awk '$1=="D"' filtra los procesos en estado D: los atascados en IO
Causas típicas: escaneo completo de tabla, escritura síncrona de logs, copia en marcha, NFS inestable
Un proceso en D no se puede matar: esperar al IO o reiniciar la máquina
vmstat 1 5 pidstat -d 1 5 ps -eo state,pid,cmd | awk '$1=="D"'
En free -h, available es lo que pueden usar procesos nuevos; un free bajo es normal
Un buff/cache alto no es problema: se recupera cuando hace falta
Usar mucho swap indica presión real; si/so sostenidos distintos de 0 es thrashing
Ordenar procesos: ps -eo pid,rss,comm --sort=-rss (-e todos, -o columnas, el menos es descendente)
RSS es el uso real; VSZ es espacio de direcciones y apenas sirve
En contenedores manda el límite cgroup (/sys/fs/cgroup/memory.max), no el total del anfitrión
Ante una fuga, comparar dos instantáneas de RSS separadas diez minutos y ver cuál solo crece
free -h vmstat 1 5 # ver si / so ps -eo pid,rss,comm --sort=-rss | head
Capa 1: curl -w descompone DNS, conexión, primer byte y total
-o /dev/null descarta el cuerpo, -s silencia el progreso y -w imprime el formato
Capa 2: curl 127.0.0.1 en el propio servidor, descartando red y puerta de enlace
Capa 3: comparar tiempos del log con $request_time y $upstream_response_time de nginx
Una diferencia grande sitúa el retraso en un intermediario (puerta, proxy, pool de conexiones)
No olvidar el log de consultas lentas: muchas «interfaces lentas» son SQL lento
Total alto con tiempo de origen bajo suele ser limitación o cola
curl -o /dev/null -s -w 'dns:%{time_namelookup} conn:%{time_connect} ttfb:%{time_starttransfer} total:%{time_total}\n' https://example.com/api
# añadir en log_format de nginx:
# $request_time $upstream_response_timeping -c 50 envía 50 paquetes y reporta loss% y mdev (jitter); un solo ping no demuestra nada
ping -M do -s 1472 prueba la MTU del camino: -M do prohíbe fragmentar y -s fija el tamaño
mtr -r -c 50: -r imprime un informe y -c fija los paquetes; supera a traceroute en pérdida por salto
El límite de ICMP puede simular pérdida: sondas TCP (nc -z, curl) son más fiables
ethtool -S NIC muestra contadores de error y descarte rx/tx (-S es statistics)
grep -iE significa ignorar mayúsculas más regex extendida
Pérdida en el mismo rack apunta a NIC o cable; entre regiones, al enlace del operador
ping -c 50 10.0.0.5 mtr -r -c 50 10.0.0.5 ethtool -S eth0 | grep -iE 'err|drop'
En una máquina desconocida, ejecutar esto para tener el panorama y luego decidir qué investigar
Cada línea está etiquetada con la pregunta que responde: no copiarlo como caja negra
Chuleta de opciones: --sort=-%cpu descendente por CPU, -h legible, -i inodos
grep -vE es invertir más regex extendida: aquí descarta sistemas virtuales como tmpfs
ss -tulnp se descompone en t/TCP, u/UDP, l/escucha, n/numérico, p/proceso
En systemctl, --failed muestra solo unidades rotas y --no-pager evita el paginador
En dmesg, -T da hora legible y -l filtra por nivel
Todo es de solo lectura, seguro en producción y listo para pegar en un ticket
echo "== 负载 =="; uptime echo "== CPU 前 5 =="; ps aux --sort=-%cpu | head -6 echo "== 内存前 5 =="; ps aux --sort=-%mem | head -6 echo "== 内存 =="; free -h echo "== 磁盘 =="; df -h | grep -vE 'tmpfs|overlay' echo "== inode =="; df -i | grep -vE 'tmpfs|overlay' echo "== 监听端口 =="; ss -tulnp echo "== 失败服务 =="; systemctl list-units --failed --no-pager echo "== 内核错误 =="; dmesg -T -l err | tail -10