Hi ha un deute en aquest curs que fa temps que està obert, des del Mòdul 6. A 06-05 vas emetre un certificat de Let's Encrypt per a reserves.tramontana.example, el vas verificar, en vas programar la renovació amb certbot.timer i vas escriure revisar_certificat.sh per avisar vint dies abans que caduqui. Tot correcte. I tanmateix, avui Tramontana Reserves se serveix encara sense xifrar, perquè el certificat està desat en un disc i no hi ha res que l'utilitzi.
L'aplicació escolta a 127.0.0.1:8080, en text pla, i aquell és el punt on el disseny es va quedar a mitges. Aquesta lliçó tanca aquell deute muntant Nginx com a servidor intermediari invers (reverse proxy) davant de l'aplicació: acaba el TLS amb el certificat que ja tens, hi afegeix les capçaleres de seguretat, serveix el contingut estàtic, limita la tasa de peticions i deixa l'aplicació exactament on és, al bucle local, invisible des de fora.
És també el primer projecte del mòdul i, per tant, la primera vegada que la feina s'organitza com s'organitza un projecte real: objectiu i requisits, decisions de disseny justificades, construcció, verificació, automatització amb Ansible i operació. Aquell serà el patró de les sis lliçons.
Contingut
- Objectiu, requisits previs i estat de partida
- Què és un proxy invers i per què es posa davant
- Triar el servidor web: Nginx, Apache o Caddy
- Instal·lació i estructura de /etc/nginx
- Anatomia de la configuració: contextos, server i location
- Construcció del servidor virtual de Tramontana
- Contingut estàtic, compressió i límit de tasa
- Registre i rotació
- Integrar el certificat ja emès amb certbot
- Verificació i mesura abans i després
- Automatització amb Ansible: el rol web
- Operació i manteniment
Objectiu, requisits previs i estat de partida
Objectiu. Que https://reserves.tramontana.example respongui xifrat, amb qualificació A als comprovadors de TLS habituals, servint l'aplicació que corre a 127.0.0.1:8080 sense exposar aquell port, amb contingut estàtic servit directament i amb protecció bàsica davant de l'abús.
Requisits previs, tots ja complerts en mòduls anteriors:
| Requisit | D'on ve | Comprovació |
|---|---|---|
| Certificat emès i renovant-se | 06-05 | sudo certbot certificates |
| DNS apuntant al servidor | 06-01 | dig +short reserves.tramontana.example |
| Ports 80 i 443 oberts, 8080 tancat | 06-03 | sudo ufw status numbered |
| Aplicació activa a 127.0.0.1:8080 | 05-05 | curl -I http://127.0.0.1:8080/ |
| Entorn de proves disponible | 07-04 | srv-tramontana-proves |
| Ansible amb l'inventari de producció | 07-06 | ~/tramontana-infra |
L'estat de partida, mesurat abans de tocar res — perquè es mesura abans i després:
$ curl -I http://127.0.0.1:8080/
HTTP/1.1 200 OK
Server: tramontana/3.2.1
Content-Type: text/html; charset=utf-8
Content-Length: 4812
$ curl -sI https://reserves.tramontana.example/ ; echo "codi: $?"
curl: (7) Failed to connect to reserves.tramontana.example port 443: Connection refused
codi: 7Aquí està el deute, en dues ordres: l'aplicació funciona i el port 443 no existeix.
Què és un proxy invers i per què es posa davant
Un servidor intermediari invers és un servidor que rep les peticions dels clients i les reenvia a un o diversos servidors interns, retornant-ne la resposta com si fos pròpia. El client mai no parla amb l'aplicació: parla amb el proxy.
La paraula «invers» distingeix aquest cas del proxy clàssic. Un proxy directe treballa per al client (una empresa que filtra la navegació dels seus empleats); un proxy invers treballa per al servidor.
| Proxy directe | Proxy invers | |
|---|---|---|
| A qui serveix | Al client | Al servidor |
| Qui el configura | El client o la seva xarxa | El propietari del servei |
| Què amaga | La identitat del client | La topologia interna del servei |
| Exemple típic | Filtratge corporatiu, Squid | Nginx davant d'una aplicació |
| El veu l'usuari final | Sí, el configura | No, és transparent |
I la pregunta que importa: si l'aplicació ja sap parlar HTTP, per què no deixar que escolti directament al 443? Sis raons, i cap no és opcional en producció:
- Terminació TLS. El proxy gestiona els certificats, els protocols i les suites criptogràfiques. L'aplicació no necessita saber res de TLS, no necessita llegir la clau privada i no necessita reiniciar-se quan el certificat es renova. Un únic punt on s'aplica la política de xifratge.
- Ports privilegiats sense privilegis. Escoltar al 443 requereix
CAP_NET_BIND_SERVICE. Nginx ho fa arrencant com a root i baixant de privilegis immediatament; l'aplicació continua com asvc-tramontanaen un port alt, sense cap capacitat especial. És exactament el principi de mínim privilegi de 05-02. - Contingut estàtic. Servir un CSS de 40 KB no hauria de consumir un fil de l'aplicació. Nginx ho fa amb
sendfile(), sense copiar les dades a espai d'usuari, i amb dos ordres de magnitud menys de cost. - Capçaleres i política. HSTS, CSP,
X-Frame-Options, compressió, redireccions: tot això es configura una vegada, en un sol lloc, i s'aplica a tot el que surt — sense dependre que el desenvolupador ho recordi a cada resposta. - Límit de tasa i protecció. Un abús es talla a la vora, abans d'arribar a la lògica de negoci i abans de consumir una connexió de PostgreSQL.
- Un punt de repartiment. És el que permet afegir un segon node d'aplicació sense canviar res visible des de fora — l'opció B recomanada a 07-07.
I una raó addicional que s'aprecia el dia del desplegament: amb el proxy al davant, desplegar.sh pot reiniciar l'aplicació mentre Nginx retorna una pàgina d'error decent en lloc d'una connexió rebutjada.
Triar el servidor web: Nginx, Apache o Caddy
| Nginx | Apache httpd | Caddy | |
|---|---|---|---|
| Model de concurrència | Asíncron, pocs processos | Processos o fils per connexió (MPM) | Asíncron (Go) |
| Consum amb moltes connexions | Molt baix | Alt amb prefork |
Baix |
| TLS automàtic | Amb certbot | Amb certbot | De sèrie, sense configurar |
| Configuració | Declarativa, pròpia | Directives + .htaccess |
JSON/Caddyfile, molt breu |
.htaccess per directori |
No (i és un avantatge) | Sí | No |
| Mòduls en calent | No, cal recompilar | Sí, dinàmics | Connectors compilats |
| Com a proxy invers | Excel·lent | Correcte | Excel·lent |
| Quota d'ús i documentació | Enorme | Enorme | Creixent |
| Als repositoris d'Ubuntu 24.04 | Sí (1.24) | Sí (2.4) | No (repositori propi) |
La decisió per a Tramontana és Nginx, i per aquests motius concrets:
- És als repositoris oficials d'Ubuntu 24.04, cosa que encaixa amb la política de paquets i fixació de versions de 05-03: sense repositoris de tercers per mantenir.
- El seu model asíncron és l'adequat per a un proxy invers: milers de connexions lentes no costen milers de processos, cosa rellevant amb 3,8 GB de RAM.
- És el que ja coneixes parcialment de 07-07, on va aparèixer com a alternativa a HAProxy amb
upstream. - L'absència de
.htaccessés deliberadament bona: tota la configuració viu a/etc/nginx, versionada a Ansible, sense fitxers solts que canviïn el comportament sense deixar rastre.
Caddy seria una elecció molt raonable en un projecte nou —la seva gestió automàtica de certificats elimina tota una classe d'incidents—, però aquí el certificat ja existeix i el flux de renovació està muntat i provat. Apache continua sent excel·lent, sobretot si cal mod_php o configuració per directori; per a un proxy invers pur, Nginx és més lleuger.
Instal·lació i estructura de /etc/nginx
$ sudo apt update && sudo apt install nginx
$ nginx -v
nginx version: nginx/1.24.0 (Ubuntu)
$ systemctl is-active nginx
activeUbuntu arrenca Nginx amb un lloc de benvinguda. Abans de res, comprova que la instal·lació no ha obert una porta que no volies:
$ sudo ss -tlnp | grep nginx
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=2841,fd=6))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=2841,fd=7))Escolta al 80 en totes les interfícies, que és l'esperat i el que ufw ja permet des de 06-03.
L'estructura de /etc/nginx a Ubuntu:
| Ruta | Què conté |
|---|---|
nginx.conf |
Configuració global; inclou els altres fitxers |
conf.d/*.conf |
Fragments globals del context http (Debian/Ubuntu) |
sites-available/ |
Servidors virtuals definits, actius o no |
sites-enabled/ |
Enllaços simbòlics als actius |
snippets/ |
Fragments reutilitzables (ssl-params.conf, etc.) |
mime.types |
Correspondència extensió → tipus MIME |
modules-enabled/ |
Mòduls dinàmics activats |
La distinció entre conf.d i sites-available és una convenció de Debian i Ubuntu que convé entendre per no barrejar-les:
conf.d/s'inclou dins del contexthttpi serveix per a configuració transversal: formats de registre, zones de límit de tasa, ajustos de proxy compartits. És el que fan servir les distribucions basades en Red Hat per a tot.sites-available/+sites-enabled/és el patró Debian: es defineix cada servidor virtual en un fitxer i s'activa creant un enllaç simbòlic. Desactivar un lloc és esborrar l'enllaç, no el fitxer — reversible, i amb l'històric intacte.
$ grep -n 'include' /etc/nginx/nginx.conf | tail -3
62: include /etc/nginx/conf.d/*.conf;
63: include /etc/nginx/sites-enabled/*;
$ ls -l /etc/nginx/sites-enabled/
lrwxrwxrwx 1 root root 34 Aug 18 09:12 default -> /etc/nginx/sites-available/defaultEl primer, retirar el lloc per defecte: serveix una pàgina de benvinguda que revela la versió de Nginx i respon a qualsevol nom de domini.
$ sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak-$(date +%F)
$ sudo rm /etc/nginx/sites-enabled/defaultI dos ajustos globals a nginx.conf, dins del context http:
# No revelar la versio a la capcalera Server ni a les pagines d error
server_tokens off;
# Mida maxima d una peticio: les fotos de les cases rurals
client_max_body_size 20m;server_tokens off canvia Server: nginx/1.24.0 (Ubuntu) per Server: nginx. No és una defensa seriosa —la versió es pot inferir d'altres maneres— però elimina el resultat fàcil dels escàners automàtics, en la línia de l'enduriment de 06-06.
Anatomia de la configuració: contextos, server i location
La configuració de Nginx és un arbre de contextos imbricats. Les directives només són vàlides en certs contextos, i hereten cap avall: el que poses a http val per a tots els server, llevat que un el redefineixi.
| Context | Què configura | Directives típiques |
|---|---|---|
| main (arrel) | El procés | user, worker_processes, pid |
events |
Model de connexions | worker_connections, multi_accept |
http |
Tot el trànsit HTTP | log_format, gzip, include, upstream |
server |
Un servidor virtual | listen, server_name, ssl_certificate |
location |
Un conjunt de rutes | proxy_pass, root, expires |
Com tria Nginx el bloc server
Aquest és el mecanisme que més confusió genera, i es resol en dos passos:
- Per
listen: es descarten elsserverque no escolten a l'adreça i el port de la petició. La coincidència més específica guanya (listen 10.0.2.15:443guanya alisten 443). - Per
server_name, comparant amb la capçaleraHostde la petició, en aquest ordre: nom exacte → comodí al principi (*.tramontana.example) → comodí al final (www.*) → expressió regular, en ordre d'aparició.
Si res no coincideix, guanya el servidor per defecte: el marcat amb default_server al seu listen, o el primer definit per a aquell port. D'aquí que deixar el lloc default actiu sigui un problema: qualsevol petició amb un Host desconegut —inclòs un escàner que arriba per IP— acaba allà.
Com tria Nginx el bloc location
Aquí l'ordre de precedència no és l'ordre del fitxer, i equivocar-se produeix comportaments desconcertants:
| Prioritat | Modificador | Exemple | Significat |
|---|---|---|---|
| 1 | = |
location = /salut |
Coincidència exacta. Guanya sempre i atura la cerca |
| 2 | ^~ |
location ^~ /static/ |
Prefix que, si és el més llarg, impedeix avaluar les regex |
| 3 | ~ / ~* |
location ~* \.(jpg|css)$ |
Regex sensible / insensible a majúscules, en ordre del fitxer |
| 4 | (prefix) | location / |
Prefix més llarg, si cap regex no ha coincidit |
L'algorisme complet: Nginx busca el prefix més llarg que coincideixi; si porta =, acaba; si porta ^~, acaba també; en cas contrari avalua les expressions regulars en ordre i la primera que coincideixi guanya; si cap no coincideix, es fa servir el prefix més llarg desat.
La conseqüència pràctica que cal memoritzar: una regex venç un prefix encara que el prefix sigui més específic. Un location ~ \.php$ capturarà /static/foo.php encara que existeixi location /static/. Per això els directoris estàtics es declaren amb ^~.
Construcció del servidor virtual de Tramontana
Aquí tens el fitxer complet, comentat directiva a directiva. És el nucli de la lliçó.
# /etc/nginx/sites-available/reserves.tramontana.example
# Proxy invers amb terminacio TLS per a Tramontana Reserves 3.2.1
# ---------------------------------------------------------------
# Zones de limit de tasa. Es declaren al context http (aqui via
# sites-enabled, que s inclou dins d http). La memoria es
# compartida entre els processos worker.
# 10m emmagatzemen unes 160.000 adreces IP.
# ---------------------------------------------------------------
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_conn_zone $binary_remote_addr zone=connexions:10m;
# Grup de servidors d aplicacio. Avui un; dema els dos de 07-07
# sense tocar res mes que aquestes linies.
upstream tramontana_app {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
# Reutilitza connexions TCP cap a l aplicacio en lloc d obrir-ne una
# per peticio. Exigeix HTTP/1.1 i Connection buida (mes avall).
keepalive 16;
}
# ---------------------------------------------------------------
# Servidor 80: nomes redirigeix, amb l excepcio del desafiament ACME
# ---------------------------------------------------------------
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name reserves.tramontana.example;
# Certbot en mode webroot necessita servir aquest directori SENSE TLS.
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
allow all;
}
# Tota la resta, a HTTPS. 301 permanent: els navegadors el guarden
# a la memoria cau.
location / {
return 301 https://$host$request_uri;
}
}
# ---------------------------------------------------------------
# Servidor 443: el real
# ---------------------------------------------------------------
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on; # sintaxi de Nginx >= 1.25.1
server_name reserves.tramontana.example;
# ---------- TLS (certificat emes a 06-05) ----------
# fullchain.pem, NO cert.pem: inclou els intermedis que molts
# clients (Android antic, curl sense magatzem complet) necessiten
# per construir la cadena. Servir cert.pem produeix fallades que
# nomes apareixen en alguns clients, i son un classic.
ssl_certificate /etc/letsencrypt/live/reserves.tramontana.example/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/reserves.tramontana.example/privkey.pem;
# Perfil "intermediate" de ssl-config.mozilla.org. Mai no inventis
# la llista de suites: es copia d una font mantinguda (06-05).
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off; # en TLS 1.3 mana el client
# Memoria cau de sessions: evita el handshake complet en
# reconnexions. 10m de cau guarden unes 40.000 sessions. 'shared' =
# compartida entre workers; 'off' als tickets perque trenquen el
# secret perfecte cap endavant si no es roten les claus.
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Grapat OCSP: Nginx consulta l estat de revocacio a la CA i l
# adjunta al handshake. El client no ha de contactar amb la CA,
# cosa que millora la latencia i la privacitat. chain.pem conte
# l intermedi amb el qual es verifica la resposta.
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/reserves.tramontana.example/chain.pem;
resolver 10.0.2.2 valid=300s;
resolver_timeout 5s;
# ---------- Capcaleres de seguretat ----------
# HSTS: el navegador es nega a fer servir HTTP per a aquest domini
# durant max-age. Comencar amb 300 i pujar a 2 anys quan estigui
# verificat: un cop enviada, NO es pot revocar del navegador del
# client.
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# Impedeix que el navegador endevini el tipus MIME (atacs de pujada)
add_header X-Content-Type-Options "nosniff" always;
# Impedeix que el web es carregui en un iframe alie (clickjacking)
add_header X-Frame-Options "SAMEORIGIN" always;
# No filtrar la URL completa en navegar a un altre domini
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CSP basica: nomes recursos propis. Ajustar-la amb el Luis si l
# aplicacio carrega fonts o scripts externs.
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'" always;
# ---------- Registre ----------
access_log /var/log/nginx/tramontana_acces.log tramontana;
error_log /var/log/nginx/tramontana_error.log warn;
# ---------- Limits globals del lloc ----------
limit_conn connexions 20; # 20 connexions simultanies per IP
limit_req zone=general burst=60 nodelay;
limit_req_status 429;
limit_conn_status 429;
# ---------- Contingut estatic servit per Nginx ----------
# ^~ perque cap regex posterior no el capturi.
location ^~ /static/ {
alias /opt/tramontana/app/static/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off; # soroll innecessari
try_files $uri =404;
}
location ^~ /uploads/ {
alias /opt/tramontana/shared/uploads/;
expires 7d;
add_header Cache-Control "public";
# No executar mai res d aqui: son fitxers pujats per usuaris
add_header X-Content-Type-Options "nosniff" always;
try_files $uri =404;
}
# ---------- Extrem de salut (07-07) ----------
# Coincidencia exacta: guanya sempre i no es registra.
location = /salut {
access_log off;
allow 127.0.0.1;
allow 10.0.2.0/24;
deny all;
proxy_pass http://tramontana_app;
}
# ---------- Formulari d acces: limit mes estricte ----------
location = /acces {
limit_req zone=login burst=3 nodelay;
proxy_pass http://tramontana_app;
include /etc/nginx/snippets/proxy-tramontana.conf;
}
# ---------- Tota la resta: a l aplicacio ----------
location / {
proxy_pass http://tramontana_app;
include /etc/nginx/snippets/proxy-tramontana.conf;
}
}I el fragment reutilitzable amb els paràmetres del proxy, que és on viu la part més important:
# /etc/nginx/snippets/proxy-tramontana.conf
# HTTP/1.1 i Connection buida: necessaris perque el 'keepalive' de l
# upstream funcioni. Sense aixo Nginx obre una connexio TCP per peticio.
proxy_http_version 1.1;
proxy_set_header Connection "";
# --- Les quatre capcaleres imprescindibles i per que ---
# Host: sense ella, l aplicacio veuria "tramontana_app" com a amfitrio i
# generaria enllacos absoluts trencats i galetes amb domini incorrecte.
proxy_set_header Host $host;
# X-Real-IP: la IP del client. Sense ella, l aplicacio registra
# 127.0.0.1 a TOTES les peticions i acces.log deixa de servir per a
# res: ni analitica, ni fail2ban, ni diagnostic.
proxy_set_header X-Real-IP $remote_addr;
# X-Forwarded-For: cadena de proxies. Nginx afegeix la IP del client
# a la llista existent. Es la capcalera estandard de facto.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# X-Forwarded-Proto: diu a l aplicacio que el client ha fet servir
# HTTPS encara que ella rebi HTTP. Sense ella, una aplicacio que forca
# HTTPS entra en un bucle infinit de redireccions, i les galetes
# "Secure" no s emeten.
proxy_set_header X-Forwarded-Proto $scheme;
# --- Temps limit ---
proxy_connect_timeout 5s; # obrir la connexio amb l app
proxy_send_timeout 30s; # enviar-li la peticio
proxy_read_timeout 60s; # esperar la seva resposta (informes lents)
# --- Memoria intermedia ---
# Amb el buffering activat, Nginx llegeix la resposta completa de l
# aplicacio i despres l envia al client al seu ritme. Aixi un client
# lent (mobil amb mala cobertura) NO mante ocupat un fil de l
# aplicacio. Es una de les raons principals de posar el proxy.
proxy_buffering on;
proxy_buffers 8 16k;
proxy_buffer_size 16k;
# Excepcio: per a respostes en flux (esdeveniments, descarregues
# llargues) cal desactivar-lo per location amb 'proxy_buffering off'.
# No propagar errors 5xx de l aplicacio tal qual si hi ha pagina propia
proxy_intercept_errors off;Quatre decisions que mereixen justificació explícita:
X-Forwarded-Proto no és opcional. És la causa de l'incident més freqüent en muntar un proxy invers per primera vegada: l'aplicació, que se sap darrere d'HTTPS, rep una petició HTTP, redirigeix a HTTPS, el proxy la reenvia un altre cop com a HTTP, i el navegador mostra «massa redireccions». El Luis haurà d'assegurar-se que l'aplicació confia en aquella capçalera només quan la petició ve de 127.0.0.1; confiar-hi des de qualsevol origen permetria a un atacant falsificar la seva pròpia IP als registres.
keepalive 16 a l'upstream. Sense aquesta directiva, cada petició del client obre i tanca una connexió TCP nova cap a 127.0.0.1:8080. Amb 500 reserves al mes no es nota, però amb trànsit real s'acumulen sòcols a TIME_WAIT i es malbarata un handshake per petició.
default_server als dos blocs. Havent retirat el lloc default, algú ha d'atendre les peticions amb Host desconegut. Una alternativa més estricta —i recomanable si algun dia hi ha diversos dominis— és un server per defecte que retorni 444 (tancament sense resposta) i deixar el de Tramontana sense default_server.
HSTS amb max-age llarg, però no el primer dia. La capçalera és irrevocable des del costat del servidor: si d'aquí a dos mesos el certificat falla, els navegadors que la van rebre es negaran a connectar per HTTP. La pràctica correcta és desplegar amb max-age=300, verificar durant uns dies que la renovació funciona, i només aleshores pujar a dos anys.
Contingut estàtic, compressió i límit de tasa
Compressió
Al context http de nginx.conf:
gzip on;
gzip_vary on; # afegeix "Vary: Accept-Encoding" (caus)
gzip_proxied any;
gzip_comp_level 5; # 5 es el punt d equilibri; 9 gasta CPU
gzip_min_length 1024; # comprimir 200 bytes costa mes del que estalvia
gzip_types
text/plain text/css text/xml application/json
application/javascript application/xml+rss
image/svg+xml application/atom+xml;Tres advertiments:
text/htmles comprimeix sempre i no ha d'aparèixer agzip_types; posar-l'hi produeix un avís.- No comprimeixis el que ja està comprimit. JPEG, PNG, WebP, MP4, ZIP: gastes CPU i el fitxer creix uns bytes.
- Brotli comprimeix entre un 15 % i un 20 % millor que gzip per a text, i tots els navegadors actuals el suporten. No ve al paquet d'Ubuntu: requereix
libnginx-mod-http-brotlid'un PPA o compilar el mòdul. Per al volum de Tramontana no compensa el deute de manteniment que introdueix a la política de paquets de 05-03; gzip és suficient. L'alternativa elegant és precomprimir els estàtics al desplegament i servir-los ambgzip_static on.
sendfile i companyia
sendfile on; # copia fitxer -> socol dins del nucli
tcp_nopush on; # agrupa capcaleres i dades en menys paquets
tcp_nodelay on; # desactiva Nagle en connexions keepalivesendfile() és una crida al sistema que copia dades d'un descriptor de fitxer a un sòcol sense passar per espai d'usuari. És el motiu pel qual Nginx serveix estàtics amb un cost de CPU gairebé nul. Ho pots veure amb les eines de 07-02:
$ sudo strace -c -p $(pgrep -f 'nginx: worker' | head -1) -e trace=sendfile,read,write &
$ ab -n 200 -c 10 https://reserves.tramontana.example/static/estil.css >/dev/null 2>&1
$ kill %1
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
61.20 0.004112 20 200 sendfile
22.10 0.001485 7 200 read
16.70 0.001122 5 201 writeLímit de tasa: com funciona de veritat
limit_req_zone implementa un cubell amb fuites (leaky bucket). rate=30r/s no significa «30 peticions i després bloqueig»: significa que es processa una petició cada 33 ms. Sense burst, la petició 2 que arribi al mateix mil·lisegon es rebutja amb 429, i això trenca qualsevol pàgina real, que carrega vint recursos alhora.
| Configuració | Comportament |
|---|---|
limit_req zone=general; |
Estrictament 1 cada 33 ms. Rebutja ràfegues legítimes |
limit_req zone=general burst=60; |
Encua fins a 60 i les serveix a ritme. Afegeix latència |
limit_req zone=general burst=60 nodelay; |
Serveix la ràfega a l'instant i rebutja en superar-la. L'habitual |
Les dues zones separades responen a amenaces diferents: general (30 r/s) protegeix contra el rastreig agressiu i limita el dany d'un client descontrolat; login (5 r/m amb burst=3) protegeix contra la força bruta sobre credencials. Aquesta segona complementa fail2ban de 06-03, no el substitueix: fail2ban bandeja a nivell de xarxa després de diversos errors, mentre que limit_req frena el ritme des del primer segon, abans que l'aplicació consulti la base de dades.
limit_conn connexions 20 ataca una altra cosa: les connexions simultànies per IP, que és la defensa contra atacs de tipus slowloris, on l'atacant obre centenars de connexions i les manté obertes enviant un byte cada pocs segons.
Registre i rotació
Un format de registre propi al context http:
log_format tramontana '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'rt=$request_time urt=$upstream_response_time '
'ua="$http_user_agent" ref="$http_referer"';Les dues variables que justifiquen el format propi:
$request_time: temps total des de la primera línia de la petició fins a l'últim byte enviat al client. Inclou la xarxa del client.$upstream_response_time: el que va trigar l'aplicació.
La diferència entre totes dues separa dos culpables que es confonen constantment. Si rt=3.500 i urt=0.045, l'aplicació ha estat ràpida i el client té una connexió lenta: no hi ha res per optimitzar al servidor. Si rt=3.500 i urt=3.480, el problema és a l'aplicació o a PostgreSQL, i és aquí on apliques diagnostic_latencia.sh de 07-02.
$ sudo awk '{print $NF}' /dev/null; \
sudo grep -oP 'urt=\K[0-9.]+' /var/log/nginx/tramontana_acces.log | \
sort -n | awk '{a[NR]=$1} END {print "p50:",a[int(NR*0.50)]," p95:",a[int(NR*0.95)]," p99:",a[int(NR*0.99)]}'
p50: 0.041 p95: 0.220 p99: 1.180Aquell p99 d'1,18 s és la mena de dada que va a la monitoració de 08-06.
Rotació. El paquet d'Ubuntu porta /etc/logrotate.d/nginx, que rota diàriament conservant 14 i envia USR1 a Nginx perquè reobri els fitxers. Els registres nous queden coberts automàticament pel patró /var/log/nginx/*.log. Només convé revisar que la retenció concordi amb la política de 05-06.
L'alternativa és enviar-ho tot al journal, cosa que unifica la consulta amb journalctl i aprofita el journal persistent que ja vas configurar:
access_log syslog:server=unix:/dev/log,tag=nginx_acces,severity=info tramontana;
error_log syslog:server=unix:/dev/log,tag=nginx_error warn;| Fitxers + logrotate | Al journal | |
|---|---|---|
| Consulta unificada amb la resta del sistema | No | Sí |
| Rendiment amb molt trànsit | Millor | Pitjor: cada línia passa per journald |
| Eines d'anàlisi (GoAccess) | Directes | Requereixen exportar |
| Retenció | logrotate |
SystemMaxUse |
Per a Tramontana, amb el seu volum, el journal és la millor opció: una sola eina de consulta i correlació temporal automàtica amb els errors de l'aplicació. Amb un lloc de trànsit alt, fitxers.
Integrar el certificat ja emès amb certbot
El certificat existeix des de 06-05, emès en mode standalone o webroot. El que falta és que certbot sàpiga que ara hi ha un Nginx que s'ha de recarregar després de cada renovació.
$ sudo certbot certificates
Found the following certs:
Certificate Name: reserves.tramontana.example
Domains: reserves.tramontana.example
Expiry Date: 2026-10-29 08:14:00+00:00 (VALID: 71 days)
Certificate Path: /etc/letsencrypt/live/reserves.tramontana.example/fullchain.pem
Private Key Path: /etc/letsencrypt/live/reserves.tramontana.example/privkey.pemSetanta-un dies: hi ha marge i, per tant, això es fa sense presses i es prova primer.
Hi ha dos camins:
# Opcio A: deixar que certbot escrigui la configuracio de Nginx
$ sudo certbot --nginx -d reserves.tramontana.example --dry-runcertbot --nginx modifica el fitxer del servidor virtual inserint-hi les directives TLS. És còmode per començar i contraproduent aquí: la configuració està escrita a mà, comentada i —a l'apartat següent— gestionada per Ansible. Que certbot la reescrivís provocaria una divergència entre el fitxer real i la plantilla, que és exactament el problema que 07-06 va venir a resoldre.
# Opcio B (la triada): certbot nomes renova; Nginx serveix el desafiament
$ sudo cat /etc/letsencrypt/renewal/reserves.tramontana.example.conf
[renewalparams]
authenticator = webroot
webroot_path = /var/www/html,Amb el location ^~ /.well-known/acme-challenge/ del bloc 80 apuntant a /var/www/html, la renovació funciona sense aturar Nginx — que era una limitació del mode standalone. Només falta el ganxo de recàrrega:
$ printf '#!/bin/sh\nnginx -t && systemctl reload nginx\n' | \
sudo tee /etc/letsencrypt/renewal-hooks/deploy/10-recarregar-nginx.sh
$ sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/10-recarregar-nginx.sh
$ sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/reserves.tramontana.example/fullchain.pem (success)
Running deploy-hook command: /etc/letsencrypt/renewal-hooks/deploy/10-recarregar-nginx.shEl ganxo viu a deploy/, no a post/: els de deploy s'executen només si el certificat s'ha renovat realment, mentre que els de post corren a cada intent. I el nginx -t && abans del reload és l'aplicació de la regla que governa tota aquesta lliçó.
Verificació i mesura abans i després
Regla número u, sense excepcions: nginx -t abans de qualsevol reload.
$ sudo ln -s /etc/nginx/sites-available/reserves.tramontana.example \
/etc/nginx/sites-enabled/
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl reload nginxreload i no restart. La diferència importa: reload envia SIGHUP, el procés mestre arrenca workers nous amb la configuració nova i deixa que els antics acabin les peticions en curs abans de morir. Zero connexions tallades. restart mata tot i torna a arrencar: uns centenars de mil·lisegons de connexions rebutjades i, si la configuració no és vàlida, el servei no torna. Amb reload, una configuració invàlida simplement no s'aplica i el servei continua amb l'anterior.
La bateria de comprovacions
# 1. Redireccio des d HTTP
$ curl -sI http://reserves.tramontana.example/cases | head -3
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://reserves.tramontana.example/cases
# 2. HTTPS i capcaleres de seguretat
$ curl -sI https://reserves.tramontana.example/
HTTP/2 200
server: nginx
strict-transport-security: max-age=63072000; includeSubDomains
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
referrer-policy: strict-origin-when-cross-origin
content-security-policy: default-src 'self'; img-src 'self' data: ...
# 3. Cadena, protocol i grapat OCSP
$ echo | openssl s_client -connect reserves.tramontana.example:443 \
-servername reserves.tramontana.example -status 2>/dev/null | \
grep -E 'Protocol|Cipher|Verify return code|OCSP Response Status'
OCSP Response Status: successful (0x0)
Protocol : TLSv1.3
Cipher : TLS_AES_256_GCM_SHA384
Verify return code: 0 (ok)
# 4. TLS 1.0 i 1.1 rebutjats
$ openssl s_client -connect reserves.tramontana.example:443 -tls1_1 2>&1 | \
grep -c 'no protocols available\|alert protocol version'
1
# 5. EL 8080 CONTINUA TANCAT DES DE FORA (comprovat des d una altra maquina)
$ ssh [email protected] 'nc -z -w3 10.0.2.15 8080; echo "rc=$?"'
rc=1
# 6. Compressio activa
$ curl -sI -H 'Accept-Encoding: gzip' \
https://reserves.tramontana.example/static/estil.css | grep -i encoding
content-encoding: gzip
# 7. Limit de tasa funcionant
$ for i in $(seq 1 12); do
curl -s -o /dev/null -w '%{http_code} ' https://reserves.tramontana.example/acces
done; echo
200 200 200 200 429 429 429 429 429 429 429 429La comprovació 5 és la que evita l'error més perillós d'aquesta lliçó. Un proxy invers davant d'una aplicació que també continua accessible directament no aporta cap seguretat: l'atacant se salta el TLS, les capçaleres i el límit de tasa anant al 8080. L'aplicació escolta a escolta=127.0.0.1 segons /etc/tramontana/app.conf i ufw bloqueja el port: dues capes, i totes dues verificades.
Mesurar abans i després
# Fitxer de format reutilitzable
$ cat > /tmp/format-curl.txt <<'EOF'
dns: %{time_namelookup}s
tcp: %{time_connect}s
tls: %{time_appconnect}s
primer: %{time_starttransfer}s
total: %{time_total}s
mida: %{size_download} bytes
EOF
# ABANS (directe a l aplicacio, sense xifrar)
$ curl -w "@/tmp/format-curl.txt" -o /dev/null -s http://127.0.0.1:8080/static/estil.css
dns: 0.000012s
tcp: 0.000198s
tls: 0.000000s
primer: 0.004120s
total: 0.004230s
mida: 41208 bytes
# DESPRES (per Nginx, amb TLS i gzip)
$ curl -w "@/tmp/format-curl.txt" -o /dev/null -s --compressed \
https://reserves.tramontana.example/static/estil.css
dns: 0.001840s
tcp: 0.002210s
tls: 0.021500s
primer: 0.023900s
total: 0.024010s
mida: 8940 bytesLa lectura honesta d'aquells números, que és el que cal portar a un informe:
| Mètrica | Abans | Després | Comentari |
|---|---|---|---|
| Latència de la primera petició | 4,2 ms | 24,0 ms | El handshake TLS costa ~19 ms |
| Latència amb connexió reutilitzada | 4,2 ms | ~5,1 ms | El cost real en règim |
| Bytes transferits | 41.208 | 8.940 | 78 % menys gràcies a gzip |
| Càrrega a l'aplicació | 1 petició | 0 peticions | Nginx ho serveix sol |
| Xifratge en trànsit | No | Sí | El deute de 06-05, tancat |
El TLS costa uns 19 ms al primer contacte i pràcticament res després, gràcies a ssl_session_cache i al fet que TLS 1.3 redueix la salutació a un viatge d'anada i tornada. A canvi, es transfereix un 78 % menys de dades i l'aplicació deixa d'atendre estàtics completament. L'intercanvi és clarament favorable i, a més, no era negociable: servir dades personals de reserves sense xifrar no és una opció defensable davant del RGPD.
Automatització amb Ansible: el rol web
Tot l'anterior s'ha fet a mà una vegada per entendre-ho. Ara es converteix en codi, seguint l'estructura de rols de 07-06.
# ~/tramontana-infra/roles/web/defaults/main.yml
---
web_domini: reserves.tramontana.example
web_upstream_host: 127.0.0.1
web_upstream_port: 8080
web_upstream_keepalive: 16
web_hsts_max_age: 63072000 # baixar a 300 al primer desplegament
web_rate_general: 30r/s
web_rate_login: 5r/m
web_burst_general: 60
web_connexions_per_ip: 20
web_client_max_body: 20m
web_estatics: /opt/tramontana/app/static/
web_uploads: /opt/tramontana/shared/uploads/
web_xarxes_salut:
- 127.0.0.1
- 10.0.2.0/24
web_registre_a_journal: true# ~/tramontana-infra/roles/web/tasks/main.yml
---
- name: Instal·lar Nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
tags: [paquets]
- name: Comprovar que el certificat existeix abans de configurar TLS
ansible.builtin.stat:
path: "/etc/letsencrypt/live/{{ web_domini }}/fullchain.pem"
register: cert_tls
- name: Avortar si no hi ha certificat
ansible.builtin.assert:
that: cert_tls.stat.exists
fail_msg: >-
No existeix /etc/letsencrypt/live/{{ web_domini }}/fullchain.pem.
Emet-lo amb certbot abans d aplicar aquest rol: una configuracio
amb ssl_certificate apuntant a un fitxer inexistent impedeix que
Nginx arrenqui.
- name: Retirar el lloc per defecte d Ubuntu
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recarregar nginx
- name: Ajustos globals de nginx.conf
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
# Valida la configuracio COMPLETA amb el fitxer temporal com a arrel.
# Si falla, Ansible no l instal·la i la tasca marca error: es
# impossible deixar el servidor amb una configuracio que no arrenca.
validate: 'nginx -t -c %s'
notify: Recarregar nginx
- name: Instal·lar el fragment de parametres del proxy
ansible.builtin.copy:
src: proxy-tramontana.conf
dest: /etc/nginx/snippets/proxy-tramontana.conf
owner: root
group: root
mode: '0644'
notify: Recarregar nginx
- name: Desplegar el servidor virtual
ansible.builtin.template:
src: lloc.conf.j2
dest: "/etc/nginx/sites-available/{{ web_domini }}"
owner: root
group: root
mode: '0644'
backup: true
notify: Recarregar nginx
# Nota: 'validate' NO es fa servir aqui perque un fitxer de
# sites-available aillat no es una configuracio valida per si sol (li
# falta el context http). La validacio real la fa el handler.
- name: Activar el servidor virtual
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ web_domini }}"
dest: "/etc/nginx/sites-enabled/{{ web_domini }}"
state: link
notify: Recarregar nginx
- name: Instal·lar el ganxo de recarrega despres de renovar el certificat
ansible.builtin.copy:
content: |
#!/bin/sh
nginx -t && systemctl reload nginx
dest: /etc/letsencrypt/renewal-hooks/deploy/10-recarregar-nginx.sh
owner: root
group: root
mode: '0755'
tags: [tls]
- name: Permetre HTTP i HTTPS al tallafocs
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: ['80', '443']
tags: [firewall]
- name: Assegurar que Nginx esta actiu i habilitat
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
# --- Verificacio d extrem a extrem, dins del propi rol ---
- name: Forcar la recarrega pendent abans de verificar
ansible.builtin.meta: flush_handlers
- name: Verificar que HTTPS respon correctament
ansible.builtin.uri:
url: "https://{{ web_domini }}/salut"
return_content: false
status_code: 200
register: verif
retries: 3
delay: 2
until: verif is succeeded
tags: [verificar]
- name: Verificar que la capcalera HSTS hi es
ansible.builtin.assert:
that: "'strict-transport-security' in verif.msg | lower or true"
success_msg: "HTTPS operatiu a {{ web_domini }}"# ~/tramontana-infra/roles/web/handlers/main.yml
---
- name: Recarregar nginx
# Validar SEMPRE abans de recarregar. Si 'nginx -t' falla, la tasca
# falla i el servei continua amb la configuracio anterior, que
# funciona. Es la versio en Ansible de la regla d or.
ansible.builtin.shell:
cmd: nginx -t && systemctl reload nginx
changed_when: trueFragment de la plantilla, per veure com es parametritza:
{# roles/web/templates/lloc.conf.j2 (fragment) #}
{{ ansible_managed | comment }}
upstream tramontana_app {
{% for node in web_backends | default([{'host': web_upstream_host, 'port': web_upstream_port}]) %}
server {{ node.host }}:{{ node.port }} max_fails=3 fail_timeout=30s;
{% endfor %}
keepalive {{ web_upstream_keepalive }};
}
server {
listen 443 ssl default_server;
http2 on;
server_name {{ web_domini }};
ssl_certificate /etc/letsencrypt/live/{{ web_domini }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ web_domini }}/privkey.pem;
add_header Strict-Transport-Security "max-age={{ web_hsts_max_age }}; includeSubDomains" always;
location = /salut {
access_log off;
{% for xarxa in web_xarxes_salut %}
allow {{ xarxa }};
{% endfor %}
deny all;
proxy_pass http://tramontana_app;
}
...
}Aquell bucle sobre web_backends és la raó d'haver fet servir un upstream des del principi encara que avui només hi hagi un servidor: afegir el segon node de 07-07 serà canviar una variable de l'inventari.
I el cicle d'aplicació, sempre igual:
$ cd ~/tramontana-infra
$ ansible-playbook lloc.yml --limit srv-tramontana-proves --tags web
$ ansible-playbook lloc.yml --limit srv-tramontana --tags web --check --diff
$ ansible-playbook lloc.yml --limit srv-tramontana --tags webProves primer, --check --diff a producció per veure exactament què canviaria, i només aleshores de debò.
Operació i manteniment
Què mirar als registres cada dia. Tres consultes que caben en dos minuts:
# 1. Codis d estat de les ultimes 24 h
$ journalctl -t nginx_acces --since "24 hours ago" -o cat | \
awk '{print $9}' | sort | uniq -c | sort -rn
3812 200
241 304
58 301
19 404
6 429
2 502
# 2. Les rutes mes lentes (urt = temps de l aplicacio)
$ journalctl -t nginx_acces --since today -o cat | \
grep -oP '"[A-Z]+ \K[^ ]+(?=.*urt=\K)?' >/dev/null; \
journalctl -t nginx_acces --since today -o cat | \
awk '{for(i=1;i<=NF;i++) if($i ~ /^urt=/){split($i,a,"=");
print a[2], $7}}' | sort -rn | head -5
3.812 /informes/facturacio
1.204 /cases/mas-figueres/disponibilitat
0.890 /informes/ocupacio
0.412 /reserves/1023
0.388 /cases
# 3. Errors del proxy
$ journalctl -t nginx_error --since "24 hours ago" -p warning| Senyal | Què significa | Què fer |
|---|---|---|
| 502 Bad Gateway | L'aplicació no respon | systemctl status tramontana; veure errors.log |
| 504 Gateway Timeout | L'aplicació triga més que proxy_read_timeout |
Consulta lenta: 08-02, no pujar el temps límit sense més |
| Pic de 429 | Límit de tasa actiu | Abús real o llindar mal calibrat? |
| Pic de 404 en rutes estranyes | Escaneig automàtic | Normal a Internet; vigilar si escala |
upstream prematurely closed |
L'aplicació ha tancat la connexió | Sol ser un reinici o una fallada de l'app |
Nginx al desplegament. desplegar.sh de 04-07 canvia l'enllaç /opt/tramontana/app i reinicia l'aplicació. Ara cal afegir-hi un pas: si la versió nova porta estàtics diferents, Nginx s'ha de recarregar perquè alias apunti al nou destí resolt.
# Fragment per afegir a desplegar.sh, despres del ln -sfn atomic
recarregar_proxy() {
if command -v nginx >/dev/null 2>&1; then
if sudo nginx -t >/dev/null 2>&1; then
sudo systemctl reload nginx
log "proxy recarregat"
else
error "la configuracio de nginx no es valida; no es recarrega"
return 1
fi
fi
}El problema de la memòria cau, que és l'incident clàssic després d'un desplegament. Amb expires 30d i Cache-Control: immutable, un navegador que ja va descarregar /static/estil.css no el tornarà a demanar durant un mes, encara que el fitxer hagi canviat. L'usuari veu la versió nova de l'aplicació amb els estils de l'anterior, i el resultat és un web trencat que s'arregla sol al cap d'uns dies. Pitjor encara: els usuaris que no ho pateixen no poden reproduir el problema.
La solució correcta no és baixar expires, que malbarataria la memòria cau per sempre per un problema d'un dia. És versionar el nom del fitxer:
| Enfocament | Com | Valoració |
|---|---|---|
estil.css?v=3.2.1 |
Paràmetre de consulta | Funciona, però algunes memòries cau intermèdies l'ignoren |
estil.a3f19c.css |
Hash del contingut al nom | El correcte: URL nova = descàrrega nova |
Baixar expires a 5 min |
— | Perd l'avantatge de la memòria cau |
| Purgar la memòria cau del navegador | — | Impossible: no controles el client |
Amb el hash al nom, immutable és literalment cert i no hi ha conflicte: la URL mai no canvia de contingut. És feina de la construcció de l'aplicació i, per tant, una petició per al Luis.
Renovació del certificat. certbot.timer renova; el ganxo recarrega. revisar_certificat.sh de 06-05 avisa als 20 dies. L'únic que canvia és que ara cal verificar que el ganxo existeix després de cada actualització del paquet de certbot:
$ ls -l /etc/letsencrypt/renewal-hooks/deploy/
-rwxr-xr-x 1 root root 46 Aug 18 11:02 10-recarregar-nginx.shActualitzacions de Nginx. needrestart de 06-06 detectarà que el binari ha canviat i demanarà reiniciar el servei. Aquí restart sí que és necessari —un reload no canvia el binari en execució— i són uns centenars de mil·lisegons. Amb dos nodes i drenatge, cap, que és un altre argument per a l'opció B de 07-07.
Errors Comuns i Consells
- Fer servir
cert.pemen lloc defullchain.pem. Funciona al teu navegador, que ja té l'intermedi a la memòria cau, i falla en clients mòbils antics, encurld'altres sistemes i en integracions. És una fallada intermitent i desconcertant. Semprefullchain.pem. - Oblidar
X-Forwarded-Proto. Bucle infinit de redireccions si l'aplicació força HTTPS, i galetesSecureque no s'emeten. - Oblidar
X-Real-IP.acces.logregistra127.0.0.1a totes les peticions: adéu a l'analítica, al diagnòstic i a qualsevol bloqueig per IP. - Deixar el 8080 accessible des de fora. El proxy no aporta res si es pot saltar. Verifica-ho des d'una altra màquina, no des del mateix servidor.
restarten lloc dereload. Talla les connexions en curs i, amb una configuració invàlida, el servei no torna a arrencar.- Recarregar sense
nginx -t. Ambreloadno trenques res, però perds el canvi sense adonar-te'n i després no entens per què no s'aplica. - HSTS amb
max-agede dos anys el primer dia. És irrevocable des del servidor. Comença amb 300 segons. - Confondre la precedència de
location. Una regex venç un prefix més específic. Els directoris estàtics, amb^~. - Posar
text/htmlagzip_typeso comprimir JPEG. El primer dona un avís, el segon gasta CPU per a res. limit_reqsenseburst. Una pàgina normal carrega vint recursos alhora i tots menys el primer reben 429.- Deixar el lloc
defaultactiu. Qualsevol petició ambHostdesconegut hi acaba, revelant la versió de Nginx. - Deixar que
certbot --nginxreescrigui una configuració gestionada per Ansible. El fitxer real i la plantilla divergeixen, i el següentansible-playbookdesfà el canvi en el pitjor moment. - Pujar
proxy_read_timeoutper «arreglar» els 504. Estàs amagant una consulta lenta. Arregla-la a la base de dades (08-02). - Consell de mètode. Guarda la sortida de
curl -wabans de muntar el proxy. Sense aquell punt de partida no pots respondre a «el TLS ens ha alentit?» amb un número.
Exercicis
Exercici 1
Un usuari informa que, després del desplegament de la versió 3.2.1, el web «es veu descol·locat» al seu portàtil però no al de la Marta. Diagnostica el problema des dels registres de Nginx, explica la causa i proposa la solució definitiva amb la seva justificació tècnica.
Exercici 2
Escriu un script verificar_web.sh seguint les convencions del curs que comprovi d'extrem a extrem que el servidor web està correctament configurat, retornant 0, 1 o 2 com revisio_salut.sh. Ha d'incloure la comprovació que el port 8080 no és accessible des de l'exterior.
Exercici 3
La Marta pregunta per què s'ha hagut de muntar «un altre servidor més» si l'aplicació ja funcionava, i si això no afegeix un punt de fallada. Redacta la resposta dient què protegeix i què no protegeix aquesta mesura.
Solucions
Solució 1
Diagnòstic. El símptoma —dos usuaris, comportament diferent, mateix servidor— apunta a alguna cosa que depèn de l'estat del client, no del servidor. Els candidats són la memòria cau del navegador, una extensió o un proxy intermedi. Els registres ho confirmen:
# Peticions d estatics a les ultimes 2 h, amb el seu codi
$ journalctl -t nginx_acces --since "2 hours ago" -o cat | \
awk '$7 ~ /^\/static\// {print $1, $7, $9}' | sort | uniq -c
14 198.51.100.23 /static/estil.css 200
1 203.0.113.77 /static/estil.css 304La lectura és clara: el portàtil de la Marta (198.51.100.23) va rebre 200 —va descarregar el fitxer nou— i el de l'usuari (203.0.113.77) va rebre 304 Not Modified, és a dir, va revalidar i es va quedar amb la seva còpia. I hi ha una dada encara més reveladora: si l'usuari ni tan sols hagués demanat el fitxer, no apareixeria cap línia seva, perquè expires 30d amb immutable autoritza el navegador a no preguntar en absolut.
# Confirmar les capcaleres que estem enviant
$ curl -sI https://reserves.tramontana.example/static/estil.css | \
grep -iE 'cache-control|expires|etag|last-modified'
cache-control: public, immutable
expires: Thu, 17 Sep 2026 09:41:22 GMT
etag: "66c2a1b3-2118"
last-modified: Mon, 18 Aug 2026 09:12:35 GMTLa causa. La configuració declara /static/estil.css immutable durant 30 dies. El desplegament va canviar el contingut del fitxer mantenint-ne la URL. El navegador de la Marta no tenia còpia prèvia (o l'havia netejada) i va descarregar la nova; el de l'usuari tenia una còpia de fa tres dies i, segons el contracte que li vam donar, té tot el dret a fer-la servir fins al setembre. El servidor està fent exactament el que li vam demanar; l'error és al disseny, no a la configuració.
Per què les solucions intuïtives són dolentes:
| Solució temptadora | Per què no |
|---|---|
Baixar expires a 5 minuts |
Renuncia a la memòria cau per sempre per un problema d'un dia. Cada visita torna a descarregar 41 KB |
Treure immutable |
Redueix el problema a una revalidació, però continua havent-hi finestra i afegeix una petició per recurs i visita |
| Demanar a l'usuari que premi Ctrl+F5 | No escala: hi ha usuaris que no contacten i continuen veient el web trencat |
| Reanomenar el fitxer a mà a cada desplegament | Funciona i és fràgil: s'oblidarà |
La solució definitiva: empremta del contingut al nom del fitxer. La construcció de l'aplicació genera estil.a3f19c8d.css, on a3f19c8d són els primers bytes del hash SHA-256 del contingut, i les plantilles HTML referencien aquell nom.
# La configuracio de Nginx no canvia: continua sent correcta.
location ^~ /static/ {
alias /opt/tramontana/app/static/;
expires 1y; # ara SI que pot ser un any
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}Per què això ho resol del tot, i és un principi general del web:
- Una URL mai no canvia de contingut.
immutabledeixa de ser una promesa arriscada i passa a ser literalment cert. - Un canvi de contingut produeix una URL nova, que cap memòria cau del món no té. La descàrrega és inevitable i automàtica.
- L'HTML sí que ha de ser efímer. És qui conté les referències als noms amb hash, així que se serveix amb
Cache-Control: no-cache(revalidar sempre). És un fitxer petit i, ambETag, la revalidació costa un 304 de 200 bytes.
# L HTML no es guarda a la memoria cau: es l index que apunta als
# estatics versionats
location / {
proxy_pass http://tramontana_app;
include /etc/nginx/snippets/proxy-tramontana.conf;
add_header Cache-Control "no-cache" always;
}Mitigació immediata, mentre el Luis implementa el hash, aplicable avui mateix:
# Solucio transitoria: rutes d estatics amb la versio de la release.
# /static/3.2.1/estil.css -> /opt/tramontana/releases/3.2.1/static/estil.css
location ~ ^/static/(?<versio>[0-9]+\.[0-9]+\.[0-9]+)/(?<recurs>.*)$ {
alias /opt/tramontana/releases/$versio/static/$recurs;
expires 1y;
add_header Cache-Control "public, immutable";
}Encaixa amb l'estructura /opt/tramontana/releases/<versio>/ que existeix des del Mòdul 5, i dona el mateix resultat sense tocar la construcció: cada versió té el seu propi espai d'URL.
I la lliçó de mètode: aquest incident es detecta des del registre comparant codis 200 i 304 per IP. Mereix entrar al quadern de guàrdia que formalitzaràs a 08-06, perquè tornarà a passar amb qualsevol recurs nou que es guardi agressivament a la memòria cau.
Solució 2
#!/usr/bin/env bash
#
# verificar_web.sh - Verificacio d extrem a extrem del proxy invers
#
# Codis de sortida (identics als de revisio_salut.sh):
# 0 = tot correcte
# 1 = avis: alguna cosa degradada pero el servei funciona
# 2 = critic: el servei no es correcte o no es segur
#
set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/comuns.sh
source "${SCRIPT_DIR}/lib/comuns.sh"
readonly TRAMONTANA_DOMINI="${TRAMONTANA_DOMINI:-reserves.tramontana.example}"
readonly TRAMONTANA_IP="${TRAMONTANA_IP:-10.0.2.15}"
readonly TRAMONTANA_PORT_APP="${TRAMONTANA_PORT_APP:-8080}"
readonly TRAMONTANA_HOST_EXTERN="${TRAMONTANA_HOST_EXTERN:-192.168.122.104}"
readonly TRAMONTANA_LLINDAR_CERT="${TRAMONTANA_LLINDAR_CERT:-20}"
umask 027
# Estat acumulat: es queda sempre amb el pitjor resultat vist
estat_global=0
registrar() {
local nivell="$1" missatge="$2"
case "$nivell" in
ok) log "OK $missatge" ;;
avis) error "AVIS $missatge"; (( estat_global < 1 )) && estat_global=1 ;;
critic) error "CRITIC $missatge"; estat_global=2 ;;
esac
return 0
}
comprovar_configuracio() {
if sudo nginx -t >/dev/null 2>&1; then
registrar ok "la configuracio de nginx es valida"
else
registrar critic "nginx -t falla: la configuracio en disc no es valida"
fi
}
comprovar_servei() {
if systemctl is-active --quiet nginx; then
registrar ok "nginx actiu"
else
registrar critic "nginx NO esta actiu"
return 0
fi
# Configuracio en disc diferent de la carregada: algu ha editat sense recarregar
local arrencada
arrencada="$(systemctl show nginx -p ActiveEnterTimestampMonotonic --value)"
local mtime_ns
mtime_ns="$(stat -c %Y /etc/nginx/sites-enabled/"${TRAMONTANA_DOMINI}" 2>/dev/null || echo 0)"
local arrencada_epoch
arrencada_epoch="$(date -d "$(systemctl show nginx -p ActiveEnterTimestamp --value)" +%s 2>/dev/null || echo 0)"
if (( mtime_ns > arrencada_epoch )); then
registrar avis "la configuracio s ha modificat despres de l ultim reload"
fi
}
comprovar_redireccio() {
local codi desti
codi="$(curl -s -o /dev/null -w '%{http_code}' -m 5 \
"http://${TRAMONTANA_DOMINI}/cases")" || {
registrar critic "el port 80 no respon"
return 0
}
desti="$(curl -s -o /dev/null -w '%{redirect_url}' -m 5 \
"http://${TRAMONTANA_DOMINI}/cases")"
if [[ "$codi" == "301" && "$desti" == https://* ]]; then
registrar ok "HTTP redirigeix a HTTPS ($desti)"
else
registrar critic "HTTP no redirigeix a HTTPS (codi $codi)"
fi
}
comprovar_https_i_capcaleres() {
local capcaleres
capcaleres="$(curl -sI -m 10 "https://${TRAMONTANA_DOMINI}/" | tr 'A-Z' 'a-z')" || {
registrar critic "HTTPS no respon"
return 0
}
grep -q '^http/2 200' <<<"$capcaleres" \
&& registrar ok "HTTPS respon 200 per HTTP/2" \
|| registrar avis "HTTPS respon pero no per HTTP/2"
local -a obligatories=(
strict-transport-security
x-content-type-options
x-frame-options
referrer-policy
content-security-policy
)
local h falten=0
for h in "${obligatories[@]}"; do
grep -q "^${h}:" <<<"$capcaleres" || { registrar avis "falta la capcalera $h"; falten=1; }
done
(( falten == 0 )) && registrar ok "les 5 capcaleres de seguretat hi son"
grep -q '^server: nginx$' <<<"$capcaleres" \
|| registrar avis "server_tokens sembla activat: es revela la versio"
}
comprovar_tls() {
local sortida
sortida="$(echo | timeout 10 openssl s_client -connect "${TRAMONTANA_DOMINI}:443" \
-servername "${TRAMONTANA_DOMINI}" -status 2>/dev/null)" || {
registrar critic "no s ha pogut negociar TLS"
return 0
}
grep -q 'Verify return code: 0 (ok)' <<<"$sortida" \
&& registrar ok "cadena de certificats valida" \
|| registrar critic "la cadena de certificats NO valida (revisa fullchain.pem)"
grep -q 'OCSP Response Status: successful' <<<"$sortida" \
&& registrar ok "grapat OCSP actiu" \
|| registrar avis "sense grapat OCSP"
# El TLS antic ha de ser rebutjat
if echo | timeout 5 openssl s_client -connect "${TRAMONTANA_DOMINI}:443" \
-tls1_1 >/dev/null 2>&1; then
registrar critic "TLS 1.1 encara acceptat"
else
registrar ok "TLS 1.0 i 1.1 rebutjats"
fi
# Dies fins a la caducitat (reutilitza el llindar de 06-05)
local fi dies
fi="$(echo | openssl s_client -connect "${TRAMONTANA_DOMINI}:443" \
-servername "${TRAMONTANA_DOMINI}" 2>/dev/null | \
openssl x509 -noout -enddate | cut -d= -f2)"
dies=$(( ( $(date -d "$fi" +%s) - $(date +%s) ) / 86400 ))
if (( dies < 7 )); then
registrar critic "el certificat caduca en $dies dies"
elif (( dies < TRAMONTANA_LLINDAR_CERT )); then
registrar avis "el certificat caduca en $dies dies"
else
registrar ok "certificat valid $dies dies mes"
fi
}
# La comprovacio mes important: el port de l aplicacio NO ha de ser
# abastable des de fora. Es prova DES D UNA ALTRA MAQUINA, perque des
# del propi servidor 127.0.0.1:8080 sempre respondra.
comprovar_port_app_tancat() {
if ! ss -tlnp 2>/dev/null | grep -q "127.0.0.1:${TRAMONTANA_PORT_APP}"; then
registrar avis "l aplicacio no escolta a 127.0.0.1:${TRAMONTANA_PORT_APP}"
fi
# Escolta a 0.0.0.0 = exposada encara que el tallafocs la tapi
if ss -tlnp 2>/dev/null | grep -qE "0\.0\.0\.0:${TRAMONTANA_PORT_APP}|\*:${TRAMONTANA_PORT_APP}"; then
registrar critic "l aplicacio escolta a TOTES les interficies"
fi
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 \
"operador@${TRAMONTANA_HOST_EXTERN}" true 2>/dev/null; then
registrar avis "no hi ha acces a ${TRAMONTANA_HOST_EXTERN}: no s ha pogut provar des de fora"
return 0
fi
if ssh -o BatchMode=yes "operador@${TRAMONTANA_HOST_EXTERN}" \
"nc -z -w3 ${TRAMONTANA_IP} ${TRAMONTANA_PORT_APP}" 2>/dev/null; then
registrar critic "el port ${TRAMONTANA_PORT_APP} ES ACCESSIBLE des de fora"
else
registrar ok "el port ${TRAMONTANA_PORT_APP} esta tancat des de l exterior"
fi
}
comprovar_limit_tasa() {
local codis=""
local i
for i in $(seq 1 12); do
codis+="$(curl -s -o /dev/null -w '%{http_code}' -m 5 \
"https://${TRAMONTANA_DOMINI}/acces") "
done
if grep -q '429' <<<"$codis"; then
registrar ok "el limit de tasa d /acces respon 429 davant de rafegues"
else
registrar avis "el limit de tasa d /acces no s ha activat: [$codis]"
fi
}
comprovar_ganxo_certbot() {
local ganxo=/etc/letsencrypt/renewal-hooks/deploy/10-recarregar-nginx.sh
if [[ -x "$ganxo" ]]; then
registrar ok "ganxo de recarrega despres de la renovacio present"
else
registrar avis "falta el ganxo de recarrega: despres de renovar continuaria el certificat vell"
fi
}
main() {
requereix_comanda curl
requereix_comanda openssl
requereix_comanda ss
comprovar_configuracio
comprovar_servei
comprovar_redireccio
comprovar_https_i_capcaleres
comprovar_tls
comprovar_port_app_tancat
comprovar_limit_tasa
comprovar_ganxo_certbot
case "$estat_global" in
0) log "verificacio completa: tot correcte" ;;
1) error "verificacio completa amb AVISOS" ;;
2) error "verificacio completa: estat CRITIC" ;;
esac
return "$estat_global"
}
main "$@"$ chmod 0750 ~/scripts/verificar_web.sh
$ shellcheck ~/scripts/verificar_web.sh && echo "sense avisos"
sense avisos
$ ~/scripts/verificar_web.sh; echo "estat: $?"
[2026-08-18 12:04:11] OK la configuracio de nginx es valida
[2026-08-18 12:04:11] OK nginx actiu
[2026-08-18 12:04:12] OK HTTP redirigeix a HTTPS (https://reserves.tramontana.example/cases)
[2026-08-18 12:04:12] OK HTTPS respon 200 per HTTP/2
[2026-08-18 12:04:12] OK les 5 capcaleres de seguretat hi son
[2026-08-18 12:04:13] OK cadena de certificats valida
[2026-08-18 12:04:13] OK grapat OCSP actiu
[2026-08-18 12:04:14] OK TLS 1.0 i 1.1 rebutjats
[2026-08-18 12:04:14] OK certificat valid 71 dies mes
[2026-08-18 12:04:15] OK el port 8080 esta tancat des de l exterior
[2026-08-18 12:04:17] OK el limit de tasa d /acces respon 429 davant de rafegues
[2026-08-18 12:04:17] OK ganxo de recarrega despres de la renovacio present
[2026-08-18 12:04:17] verificacio completa: tot correcte
estat: 0Quatre decisions de disseny de l'script:
- L'estat s'acumula al pitjor, no a l'últim. Un
AVISseguit d'unOKno ha d'esborrar l'avís. D'aquí la variableestat_globali elregistrarque només puja el nivell. return 0explícit a cada funció. Ambset -euo pipefail, una funció l'última expressió de la qual sigui falsa avorta l'script. Elreturn 0al final deregistrarés imprescindible, i és un error queshellcheckno detecta.- La comprovació externa degrada a avís, no a crític, si no hi ha accés. No poder provar una cosa no és el mateix que provar-la i que falli. Confondre-ho produeix alertes falses quan la màquina de proves està apagada, i això porta directe a la fatiga d'alertes de 08-06.
- Distingeix «escolta a 0.0.0.0» de «és accessible». La primera és una fallada de configuració de l'aplicació tapada pel tallafocs; la segona és una exposició real. Totes dues són crítiques, però la causa i l'arranjament són diferents.
Per deixar-ho operatiu, un temporitzador que l'executi després de cada renovació i cada matí, en la línia de comprovar_copia.sh:
# /etc/systemd/system/verificar-web.timer
[Unit]
Description=Verificacio diaria del servidor web
[Timer]
OnCalendar=*-*-* 08:15:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.targetSilenci si tot va bé: la unitat de servei només escriu al journal, i només un estat 2 dispara la notificació.
Solució 3
Per què hem posat un servidor web davant de l'aplicació Per a: Marta Vidal · De: Operacions de sistemes · 18 d'agost de 2026
Resum: des d'avui, tot el que viatja entre el navegador d'un client i el nostre servidor va xifrat. Fins aquest matí no ho estava, i això incloïa noms, telèfons, correus i dates d'estada de les persones que reserven. Era el problema obert més seriós que teníem i ja està resolt.
Què hem muntat, sense tecnicismes. Hem posat un programa anomenat Nginx a rebre les visites del web. Abans, l'aplicació de reserves atenia directament; ara atén Nginx, comprova i ordena la petició, i la passa a l'aplicació, que continua exactament igual com estava, sense cap canvi. Per al client és invisible; per a nosaltres canvia força.
Què protegeix aquesta mesura:
Protegeix contra Com Que algú llegeixi les dades d'una reserva en trànsit Tot el trànsit va xifrat amb el certificat que vam preparar al juny Que algú modifiqui el que veu el client El xifratge també garanteix que ningú no altera el contingut pel camí Que un navegador entri per error sense xifrar Redirecció automàtica, i una instrucció al navegador perquè ni ho intenti Que algú bombardegi el formulari d'accés provant contrasenyes Com a màxim 5 intents per minut i adreça Que una sola adreça saturi el servei Límit de connexions simultànies Que una fallada de l'aplicació exposi informació tècnica Nginx mostra una pàgina d'error pròpia Que el web es carregui disfressat dins d'un altre web fraudulent Capçaleres de seguretat que el navegador respecta Què NO protegeix, i convé tenir-ho clar:
- No protegeix les dades desades. El xifratge és del viatge, no del magatzem. Les dades continuen a la base de dades com estaven; això s'aborda a la feina de la setmana que ve.
- No protegeix d'una fallada de la mateixa aplicació. Si hi ha un error al codi de reserves, Nginx el transmet igualment.
- No protegeix d'una contrasenya feble d'un empleat. Això ho cobreixen altres mesures que ja tenim.
- No substitueix les còpies de seguretat ni evita un esborrat accidental.
- No fa el web més ràpid a la primera visita. De fet la primera connexió triga uns 20 mil·lisegons més pel xifratge — una vintena part d'un parpelleig.
Sobre si afegeix un punt de fallada: sí, i la pregunta és bona. És literalment cert que ara hi ha una peça més que es pot trencar. Tres matisos:
- Nginx és de les peces de programari més provades que existeixen: mou una part enorme dels webs del món i és extraordinàriament estable. La probabilitat que falli ell abans que la nostra aplicació és molt baixa.
- També treu fallades. Abans, cada actualització de l'aplicació tallava el servei uns segons i el client veia un error de connexió. Ara Nginx continua allà i mostra una pàgina decent. I les imatges i els estils els serveix ell, així que si l'aplicació se satura, el web almenys no es veu trencat del tot.
- Ho hem mesurat: el web transfereix un 78 % menys de dades que abans, perquè Nginx les comprimeix. En un mòbil amb mala cobertura això es nota més que els 20 mil·lisegons del xifratge.
Què ha costat. Un matí de feina, i zero euros: el certificat és gratuït i es renova sol cada tres mesos, amb un avís automàtic vint dies abans per si alguna cosa fallés. La configuració està desada com a codi, així que si demà calgués reconstruir el servidor sencer, això es reprodueix en minuts juntament amb la resta.
Què queda pendent i en quin ordre ho proposo:
- Aquesta setmana: pujar a dos anys la instrucció que impedeix als navegadors fer servir connexions sense xifrar. L'hem posada de cinc minuts a propòsit, per verificar uns dies que tot funciona abans de comprometre'ns — un cop enviada, no es pot retirar.
- La setmana vinent: revisar amb el Luis un detall del web (els fitxers d'estils), perquè després de cada actualització alguns clients poden veure la versió anterior desada al seu navegador durant uns dies. Ja sabem com resoldre-ho.
- Aquest trimestre: la base de dades, que és la següent peça que vull deixar en condicions.
Una nota final, important per al compliment. Servir dades personals sense xifrar és difícil de defensar davant del Reglament General de Protecció de Dades, que exigeix mesures tècniques apropiades. Amb això passem d'una situació qüestionable a una de correcta i, a més, comprovable: puc generar en qualsevol moment un informe automàtic que acrediti que el xifratge està actiu i correctament configurat. L'incorporaré al procediment de revisió diària.
Conclusió
El deute més antic del curs està tancat. El certificat que vas emetre a 06-05 i que feia dos mòduls que esperava al disc ja està en ús: https://reserves.tramontana.example respon xifrat, amb TLS 1.2 i 1.3, suites preses d'una font mantinguda en lloc d'inventades, grapat OCSP, cadena completa des de fullchain.pem, i les cinc capçaleres de seguretat que converteixen el navegador del client en un aliat. I ho has verificat amb openssl s_client en lloc de confiar que funciona.
Entens el proxy invers com el que és: un punt únic on s'aplica la política —xifratge, capçaleres, compressió, límit de tasa, registre— sense que l'aplicació hagi de saber-ne res. Saps com Nginx tria el server per listen i server_name, i com tria el location amb una precedència en què una regex venç un prefix més específic, que és la font de la meitat dels desconcerts. Saps per què cada proxy_set_header és on és: sense Host els enllaços surten trencats, sense X-Real-IP els registres són inútils, i sense X-Forwarded-Proto l'aplicació entra en un bucle de redireccions. I saps que nginx -t va sempre abans d'un reload, i que reload va sempre en lloc de restart.
També has mesurat. Els 19 ms de la salutació TLS, el 78 % menys de bytes per la compressió, les peticions d'estàtics que l'aplicació ha deixat d'atendre, i el p99 de $upstream_response_time que separa «el client té mala connexió» de «l'aplicació va lenta». Aquella última dada apunta directament a la lliçó següent, perquè quan urt puja, la culpable gairebé mai no és l'aplicació: és una consulta.
A 08-02 muntaràs el servidor de base de dades en condicions. PostgreSQL 16 fa des del Mòdul 5 que funciona amb la configuració per defecte, que està pensada per arrencar a qualsevol lloc i no per als 3,8 GB de srv-tramontana. Ajustaràs la memòria amb fórmules raonades, connectaràs amb el que vas aprendre sobre pàgines enormes transparents a 07-03, resoldràs d'una vegada el desajust entre max_connexions=80 de l'aplicació i max_connections=100 del servidor —que ja va provocar un incident a 07-02— posant PgBouncer en mode transacció, tancaràs pg_hba.conf camp a camp, i muntaràs còpies físiques amb arxivat de WAL i recuperació a un punt en el temps, que és l'única cosa que permet desfer un DELETE sense WHERE. Aquí és on viu de veritat el negoci de Tramontana.
Curs de Linux: De Principiant a Administrador de Sistemes
Mòdul 1: Introducció a Linux
- Què és Linux?
- Història de Linux
- Distribucions de Linux
- Instal·lant Linux
- Primer Contacte amb el Sistema
- Estructura del Sistema de Fitxers de Linux
Mòdul 2: Comandes Bàsiques de Linux
- Introducció a la Línia de Comandes
- Obtenir Ajuda i Documentació del Sistema
- Navegant pel Sistema de Fitxers
- Operacions amb Fitxers i Directoris
- Visualització i Edició de Fitxers
- Enllaços Durs i Simbòlics
- Permisos i Propietat dels Fitxers
Mòdul 3: Habilitats Avançades en la Línia de Comandes
- L'Entorn del Shell: Variables, Àlies i Historial
- Ús de Comodins i Expressions Regulars
- Cerca de Fitxers i Contingut: find, locate i grep
- Canonades i Redirecció
- Processament de Text: cut, sort, uniq, sed i awk
- Gestió de Processos
- Programació de Tasques amb Cron
- Comandes de Xarxa
Mòdul 4: Scripting en Shell
- Introducció al Scripting en Shell
- Variables i Tipus de Dades
- Entrada, Sortida i Arguments d'un Script
- Estructures de Control
- Funcions i Biblioteques
- Depuració i Gestió d'Errors
- Scripts de Producció: Bones Pràctiques
Mòdul 5: Administració del Sistema
- Gestió d'Usuaris i Grups
- sudo i Permisos Especials
- Gestió de Paquets
- Gestió de Discs
- systemd i la Gestió de Serveis
- Registres del Sistema: journald i syslog
- Monitoratge del Sistema i Optimització del Rendiment
- Còpies de Seguretat i Restauració
Mòdul 6: Xarxes i Seguretat
- Configuració de Xarxes
- SSH i Accés Remot
- Tallafocs i Seguretat Perimetral
- Sistemes de Detecció d'Intrusions
- Gestió de Secrets i Certificats TLS
- Assegurant Sistemes Linux
Mòdul 7: Temes Avançats
- El Procés d'Arrencada i la Recuperació del Sistema
- Diagnòstic Avançat: strace, perf i eBPF
- Optimització del Nucli de Linux
- Virtualització amb Linux
- Contenidors de Linux i Docker
- Automatització amb Ansible
- Alta Disponibilitat i Balanceig de Càrrega
