Tareas administrativas
LPI 107.1 Usuarios y gruposLPI 107.2 Automatización (cron/at)LPI 107.3 Localización e internacionalización
Este módulo es el corazón del trabajo diario de un administrador de sistemas: quién puede entrar al sistema, qué puede hacer una vez dentro, y qué tareas se ejecutan solas sin que nadie tenga que acordarse de lanzarlas a mano.
- Crear, modificar y eliminar usuarios y grupos, y entender
/etc/passwd,/etc/shadowy/etc/groupcampo a campo. - Gestionar el envejecimiento de contraseñas con
chage. - Programar tareas recurrentes con
crony tareas puntuales conat. - Configurar el idioma, la codificación de caracteres y la zona horaria del sistema.
1. LPI 107.1 — Gestionar usuarios y grupos
1.1 Crear, modificar y borrar usuarios
| Comando | Qué hace |
|---|---|
useradd usuario | Crea un usuario — sin -m no crea directorio personal, al contrario de lo que la mayoría espera |
useradd -m -s /bin/bash -c "Jose Perez" jose | Crea usuario con directorio personal (-m, copiando /etc/skel), shell de login (-s) y comentario/nombre completo (-c) |
usermod -aG grupo usuario | Añade el usuario a un grupo adicional — el -a (append) es crítico: sin él, -G reemplaza todos los grupos secundarios del usuario |
usermod -L usuario / -U | Bloquea / desbloquea la cuenta (antepone/quita un ! al hash en /etc/shadow) |
usermod -s /usr/sbin/nologin usuario | Impide el login interactivo sin borrar la cuenta — típico en cuentas de servicio |
userdel usuario | Borra el usuario, conserva su directorio personal |
userdel -r usuario | Borra el usuario y su directorio personal y su buzón de correo |
$ useradd -m -s /bin/bash -c "Jose Perez" jose$ usermod -aG sudo,docker jose$ id jose
uid=1001(jose) gid=1001(jose) groups=1001(jose),27(sudo),998(docker)usermod -G sin -a es de los favoritos del examen: usermod -G docker jose (sin -a) elimina a jose de sudo si no lo incluyes también en la lista — porque -G solo sin modificador reemplaza la lista completa de grupos secundarios, no la amplía.1.2 Grupos
| Comando | Qué hace |
|---|---|
groupadd nombre | Crea un grupo nuevo |
groupmod -n nuevo_nombre viejo_nombre | Renombra un grupo |
groupdel nombre | Borra un grupo (falla si es el grupo primario de algún usuario) |
groups usuario | Lista los grupos a los que pertenece |
1.3 /etc/passwd, campo a campo
$ grep jose /etc/passwd
jose:x:1001:1001:Jose Perez:/home/jose:/bin/bash| # | Campo | Valor del ejemplo |
|---|---|---|
| 1 | Nombre de usuario | jose |
| 2 | Contraseña | x — indica que el hash real vive en /etc/shadow, no aquí |
| 3 | UID | 1001 |
| 4 | GID primario | 1001 |
| 5 | GECOS (nombre completo/comentario) | Jose Perez |
| 6 | Directorio personal | /home/jose |
| 7 | Shell de login | /bin/bash |
/etc/passwd tiene que seguir siendo legible por todos los usuarios (muchísimos comandos lo consultan para traducir UID a nombre), así que el hash de la contraseña se trasladó hace décadas a /etc/shadow, que no es legible por usuarios normales — por eso en el módulo 104 viste que ni siquiera tu propio usuario puede leer directamente ese fichero.1.4 /etc/shadow, campo a campo
$ sudo grep jose /etc/shadow
jose:$6$abcd1234$hashmuylargo...:19987:0:90:7:30::| # | Campo | Valor del ejemplo | Significado |
|---|---|---|---|
| 1 | Usuario | jose | — |
| 2 | Hash de la contraseña | $6$... | $6$ = SHA-512; ! o !! = cuenta bloqueada; vacío = sin contraseña |
| 3 | Último cambio | 19987 | Días desde el 1 de enero de 1970 (época Unix) del último cambio de contraseña |
| 4 | Días mínimos | 0 | Días que deben pasar antes de poder cambiarla de nuevo |
| 5 | Días máximos | 90 | Días que puede seguir siendo válida antes de forzar un cambio |
| 6 | Días de aviso | 7 | Días antes de caducar en los que se avisa al usuario al entrar |
| 7 | Días de inactividad | 30 | Días de margen tras caducar antes de bloquear la cuenta definitivamente |
| 8 | Fecha de expiración | (vacío) | Fecha absoluta (en días desde 1970) en la que la cuenta deja de existir, independientemente de la contraseña |
| 9 | Reservado | (vacío) | Sin uso actualmente |
1.5 Envejecimiento de contraseñas: chage
$ chage -l jose
Last password change : sep 15, 2026
Password expires : dic 14, 2026
Password inactive : ene 13, 2027
Account expires : never
Minimum number of days between password change : 0
Maximum number of days between password change : 90
Number of days of warning before password expires : 7$ chage -M 60 -W 14 jose$ chage -E 2027-01-01 jose
# fuerza que la cuenta expire en esa fecha exactachage se puede leer y editar también directamente en los campos 3-8 de /etc/shadow — chage es simplemente la forma segura y legible de hacerlo, sin arriesgarte a estropear el formato del fichero a mano.1.6 /etc/group y comandos de identidad
$ grep sudo /etc/group
sudo:x:27:jose,mariaCuatro campos: nombre del grupo, x (igual que en passwd, placeholder), GID, y la lista de usuarios miembros secundarios separados por comas — los usuarios cuyo grupo primario es este no aparecen aquí, ya están vinculados por el campo 4 de /etc/passwd.
$ whoami
jose$ id
uid=1001(jose) gid=1001(jose) groups=1001(jose),27(sudo)1.7 Valores por defecto: /etc/login.defs y /etc/default/useradd
$ grep -E '^UID_MIN|^UID_MAX|^PASS_MAX_DAYS' /etc/login.defs
UID_MIN 1000
UID_MAX 60000
PASS_MAX_DAYS 90/etc/login.defs fija los valores por defecto que usan useradd/chage cuando no se les da un valor explícito: a partir de qué UID empiezan los usuarios normales (por debajo quedan reservados para cuentas de sistema), y la caducidad de contraseña por defecto, entre otros.
Practica más — labs ya publicados en Ghost Academy sobre este tema: 👥 Usuarios & grupos (lab) · 🔑 Usuarios & permisos (lab)
2. LPI 107.2 — Automatizar tareas con cron y at
2.1 cron: tareas recurrentes
$ crontab -e
# abre el editor configurado (ver $EDITOR / update-alternatives) con el crontab del usuario actual$ crontab -l
# backup diario a las 2:30 de la madrugada
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1$ crontab -r
# BORRA el crontab completo del usuario actual, sin confirmar - cuidadoCinco campos de tiempo, en este orden, antes del comando:
| Campo | Rango |
|---|---|
| Minuto | 0-59 |
| Hora | 0-23 |
| Día del mes | 1-31 |
| Mes | 1-12 |
| Día de la semana | 0-7 (0 y 7 son ambos domingo) |
Un * significa "cualquier valor". Se combinan con , (lista), - (rango) y / (paso):
$ cat miscrontab.txt
0 */4 * * * /scripts/sincroniza.sh
15,45 9-17 * * 1-5 /scripts/recordatorio.sh
0 0 1 * * /scripts/informe_mensual.sh| Línea | Se ejecuta |
|---|---|
0 */4 * * * | Cada 4 horas en punto (0:00, 4:00, 8:00...) |
15,45 9-17 * * 1-5 | A los minutos 15 y 45 de cada hora, entre las 9 y las 17, de lunes a viernes |
0 0 1 * * | El día 1 de cada mes, a medianoche |
@reboot (al arrancar el sistema), @daily (equivalente a 0 0 * * *), @hourly, @weekly, @monthly, @yearly. El examen las pregunta tanto en su forma literal como pidiendo la expresión de cinco campos equivalente.2.2 /etc/crontab y /etc/cron.d/: la diferencia con crontab -e
$ cat /etc/crontab
# m h dom mon dow user command
17 * * * * root run-parts /etc/cron.hourly
25 6 * * * root run-parts /etc/cron.daily
47 6 * * 7 root run-parts /etc/cron.weeklycrontab -e, guardado en /var/spool/cron/crontabs/) tiene 5 campos de tiempo + comando. /etc/crontab y los ficheros en /etc/cron.d/ tienen un sexto campo extra — el usuario con el que se ejecuta la tarea — precisamente porque root puede definir ahí tareas para cualquier usuario, no solo las suyas.Las carpetas /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/ y /etc/cron.monthly/ contienen scripts completos (no líneas de crontab) que run-parts ejecuta todos, uno a uno, en el momento indicado — la forma habitual de que un paquete instalado añada su propia tarea periódica sin tocar ningún crontab existente.
$ cat /etc/cron.allow 2>/dev/null; cat /etc/cron.deny 2>/dev/null
joseSi existe /etc/cron.allow, solo los usuarios listados ahí pueden usar crontab. Si no existe pero sí existe /etc/cron.deny, pueden todos salvo los listados. Si no existe ninguno de los dos, el comportamiento por defecto depende de la distro (en Debian/Ubuntu, todos pueden).
2.3 at: una única ejecución puntual
$ at 23:00
at> /usr/local/bin/apagar_servicios.sh
at> <EOT>
job 7 at Wed Sep 30 23:00:00 2026$ at now + 2 hours
# acepta expresiones relativas: minutes, hours, days, weeks$ atq
7 Wed Sep 30 23:00:00 2026 a jose$ atrm 7
# cancela el trabajo numero 7 antes de que se ejecutecron | at | |
|---|---|---|
| Para qué | Tareas que se repiten | Una única ejecución en el futuro |
| Listar pendientes | crontab -l | atq |
| Cancelar | Editar con crontab -e y borrar la línea | atrm <job> |
| Control de acceso | /etc/cron.allow / /etc/cron.deny | /etc/at.allow / /etc/at.deny (misma lógica) |
anacron: en vez de depender de que el reloj marque exactamente la hora programada, comprueba al arrancar si una tarea diaria/semanal/mensual se quedó sin ejecutar mientras el equipo estaba apagado, y la lanza entonces. En sistemas con systemd, los timers (systemctl list-timers, módulo 101) son la alternativa moderna que cubre ambos casos a la vez.Practica más — labs ya publicados en Ghost Academy sobre este tema: ⏰ Cron (lab) · 📅 Crontab: programando tareas (lab)
Herramienta interactiva — del Toolkit de Ghost Academy: 🛠️ CronCraft: generador visual de crontab
3. LPI 107.3 — Localización e internacionalización
3.1 Idioma y codificación: locale
$ locale
LANG=es_ES.UTF-8
LC_CTYPE="es_ES.UTF-8"
LC_NUMERIC="es_ES.UTF-8"
LC_TIME="es_ES.UTF-8"
LC_COLLATE="es_ES.UTF-8"
LC_MONETARY="es_ES.UTF-8"
LC_MESSAGES="es_ES.UTF-8"
LC_ALL=| Variable | Controla |
|---|---|
LANG | Valor por defecto para todas las categorías LC_* que no se fijen explícitamente — la que normalmente basta con cambiar |
LC_TIME | Formato de fechas y horas |
LC_NUMERIC | Separador decimal y de miles |
LC_MONETARY | Formato de cantidades monetarias |
LC_COLLATE | Orden alfabético (afecta a sort, módulo 103) |
LC_MESSAGES | Idioma de los mensajes del sistema y los programas |
LC_ALL | Sobrescribe todas las demás LC_* y a LANG — pensada sobre todo para forzar un idioma concreto puntualmente en un único comando, no para uso permanente |
$ LC_ALL=C date
Wed Sep 30 18:30:03 UTC 2026LC_ALL=C (o LC_ALL=POSIX) antes de un comando es el truco clásico para forzar salida en inglés y formato "neutro" — muy usado al escribir scripts, para que su salida no cambie según el idioma configurado en la máquina donde se ejecuten.3.2 Generar e instalar locales
$ cat /etc/locale.gen | grep -v '^#' | grep .
es_ES.UTF-8 UTF-8$ locale-gen
Generating locales (this might take a while)...
es_ES.UTF-8... done
Generation complete.$ update-locale LANG=es_ES.UTF-8En Debian/Ubuntu, el proceso completo para añadir un idioma nuevo es: descomentar la línea correspondiente en /etc/locale.gen (o usar dpkg-reconfigure locales, que hace lo mismo de forma interactiva), ejecutar locale-gen para generarla de verdad, y update-locale para fijarla como la activa por defecto en /etc/default/locale.
3.3 Zona horaria
$ timedatectl
Local time: mié 2026-09-30 18:30:03 UTC
Universal time: mié 2026-09-30 18:30:03 UTC
RTC time: mié 2026-09-30 18:30:03
Time zone: Europe/Madrid (CEST, +0200)
System clock synchronized: yes
NTP service: active$ timedatectl list-timezones | grep Madrid
Europe/Madrid$ timedatectl set-timezone Europe/MadridLa configuración persiste como un enlace simbólico en /etc/localtime apuntando a la zona horaria correspondiente dentro de /usr/share/zoneinfo/, y el nombre en texto plano se guarda además en /etc/timezone. En sistemas más antiguos sin timedatectl, el equivalente es dpkg-reconfigure tzdata.
3.4 Convertir entre codificaciones: iconv
$ file documento.txt
documento.txt: ISO-8859 text$ iconv -f ISO-8859-1 -t UTF-8 documento.txt -o documento_utf8.txtñ en vez de ñ) casi siempre es un problema de codificación, no de contenido — se creó en una codificación antigua como ISO-8859-1 (Latin-1) y se está leyendo asumiendo UTF-8, o al revés. iconv convierte de una a otra sin tener que reescribir el fichero a mano.Práctica guiada
- Crea un usuario nuevo con directorio personal y shell bash, añádelo a un grupo secundario con
usermod -aG, y confirma conid. - Consulta su entrada completa en
/etc/passwdy (como root) en/etc/shadow, e identifica cada campo con las tablas de este módulo. - Configúrale una caducidad de contraseña de 60 días con
chage -M 60y compruébalo conchage -l. - Programa con
crontab -euna tarea que escriba la fecha en un fichero cada 5 minutos (*/5 * * * * date >> /tmp/marca.txt), espera y comprueba el resultado. - Programa una tarea puntual con
at now + 2 minutesy revísala conatqantes de que se ejecute. - Comprueba tu
localeactual, y prueba a ejecutarLC_ALL=C datepara ver la diferencia de formato. - Revisa tu zona horaria con
timedatectly confirma que coincide con la que esperas.
Resumen de comandos del módulo
| Bloque | Comandos clave |
|---|---|
| 107.1 Usuarios y grupos | useradd, usermod, userdel, groupadd/groupmod/groupdel, chage, id/groups/whoami |
| 107.2 Automatización | crontab -e/-l/-r, /etc/cron.d/, run-parts, at, atq, atrm |
| 107.3 Localización | locale, locale-gen, update-locale, timedatectl, iconv |
Autoevaluación
- ¿Por qué
usermod -G docker jose(sin-a) puede ser peligroso sijoseya pertenecía al gruposudo? - ¿Qué campo de
/etc/passwdcontiene el placeholderx, y dónde vive el dato real? - ¿Qué diferencia de formato hay entre el crontab de un usuario (
crontab -e) y/etc/crontab? - ¿Qué cadena especial de cron equivale a
0 0 * * *? - ¿Cuándo usarías
aten vez decron? - ¿Qué variable fijarías para forzar que un único comando muestre su salida en formato "neutro" sin cambiar la configuración permanente del sistema?
- Un fichero de texto se ve con símbolos raros en vez de tildes. ¿Qué herramienta usarías para solucionarlo, y qué necesitas saber antes de usarla?