PCI-DSS (Payment Card Industry Data Security Standard) es la norma de seguridad que debe cumplir cualquier comercio que acepte tarjetas de pago, y la versión vigente es la 4.0.1. Para una tienda online, la decisión que más trabajo ahorra no es técnica sino de arquitectura: que los datos de la tarjeta nunca pasen por tu servidor. En esta guía verás cómo reducir el alcance y después aplicarás, en un servidor web con Ubuntu 24.04, los controles de PCI-DSS que dependen del administrador de sistemas, con un comando de verificación para cada uno.
Notaesta guía no sustituye a la evaluación formal. Tu banco adquirente te indicará si debes presentar un cuestionario de autoevaluación (SAQ) o una auditoría con un QSA, y el texto oficial de cada requisito está en el sitio del PCI Security Standards Council.
Requisitos previos
- Un servidor web con Ubuntu 24.04 LTS que aloja la tienda, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudoy acceso SSH por clave pública. - Nginx sirviendo la tienda con un certificado TLS válido.
- La IP fija desde la que administras el servidor (la de tu oficina, VPN o bastión), que en esta guía aparece como
your_admin_ip.
Paso 1: Reducir el alcance antes de bastionar nada
Todo sistema que almacena, procesa o transmite datos de titulares de tarjeta forma parte del entorno de datos de titulares (CDE), y también los sistemas que pueden afectar a su seguridad. Cuanto menos toque la tarjeta tu servidor, menos requisitos se aplican:
| Integración del pago | Quién recibe los datos de la tarjeta | Cuestionario habitual |
|---|---|---|
| Redirección a la página del proveedor o iframe alojado por él | Solo el proveedor de pagos | SAQ A |
| Formulario en tu página que envía los datos al proveedor con JavaScript | El navegador, con código servido por ti | SAQ A-EP |
| Tu servidor recibe el número de tarjeta y lo reenvía | Tu servidor | SAQ D |
Con SAQ A tu servidor sigue siendo relevante: sirve la página que redirige o contiene el iframe, y un atacante que la modifique puede sustituir el formulario por uno falso. Por eso los controles de esta guía se aplican igualmente, aunque el número de requisitos sea mucho menor.
Hay datos que no puedes almacenar en ningún caso después de la autorización: el código de verificación (CVV2/CVC2), el PIN y los datos completos de la banda o del chip. Si almacenas el número de tarjeta (PAN), debe ser ilegible (requisito 3.5) y solo puedes mostrar como máximo los 6 primeros y 4 últimos dígitos.
Comprueba que no hay números de tarjeta en logs ni en el código. Este patrón busca números de 13 a 16 dígitos que empiezan como Visa, Mastercard o American Express; puede dar falsos positivos, así que revisa cada coincidencia:
sudo grep -rIlE '\b(4[0-9]{12}([0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13})\b' /var/log /var/www 2>/dev/null
Si aparece algún archivo con números de tarjeta reales, averigua qué componente los escribe y corrígelo antes de seguir.
Paso 2: Restringir el tráfico con un cortafuegos (requisito 1)
El requisito 1 exige controlar el tráfico entrante y saliente del CDE y permitir solo lo necesario. En un servidor web, eso significa HTTPS abierto a todo el mundo y SSH solo desde tu IP de administración.
Configura la política por defecto y las reglas:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from your_admin_ip to any port 22 proto tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
El puerto 80 solo es necesario para redirigir a HTTPS y renovar certificados de Let's Encrypt. Comprueba el resultado:
sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN your_admin_ip
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere
[ 4] 80/tcp (v6) ALLOW IN Anywhere (v6)
[ 5] 443/tcp (v6) ALLOW IN Anywhere (v6)
Comprueba también que la base de datos no escucha en interfaces públicas. MySQL o Redis deben aparecer en 127.0.0.1 o en una IP privada:
sudo ss -tlnp
PCI-DSS pide revisar las reglas del cortafuegos al menos cada seis meses (requisito 1.2.7). Guarda la salida de ufw status numbered con la fecha de cada revisión como evidencia.
Paso 3: Eliminar servicios y software innecesarios (requisito 2)
Cada servicio que no se usa es superficie de ataque. Lista los servicios en ejecución:
systemctl list-units --type=service --state=running --no-pager
Desactiva los que no necesites. Por ejemplo, si no usas impresión ni descubrimiento de red local:
sudo systemctl disable --now cups.service avahi-daemon.service
Si el servicio no existe en tu servidor, systemctl lo indicará y no ocurre nada. Cambia también todas las contraseñas y cuentas por defecto de la aplicación: el usuario administrador del CMS de la tienda, paneles de gestión y bases de datos (requisito 2.2.2).
Paso 4: Proteger el acceso administrativo (requisito 8)
SSH solo con clave
Crea un archivo de configuración con un prefijo bajo para que prevalezca sobre otros archivos de /etc/ssh/sshd_config.d/, donde gana el primer valor leído:
sudo nano /etc/ssh/sshd_config.d/01-pci.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
Advertenciaantes de recargar SSH, comprueba en otra sesión que entras con tu clave y que tu IP coincide con la regla del cortafuegos.
Valida, recarga y comprueba la configuración efectiva:
sudo sshd -t
sudo systemctl reload ssh
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|maxauthtries)'
permitrootlogin no
maxauthtries 3
passwordauthentication no
Cada persona debe tener su propia cuenta (requisito 8.2.1); no compartas un usuario deploy o admin entre varias personas.
Contraseñas de 12 caracteres
Aunque SSH no use contraseñas, sudo sí. PCI-DSS 4.0 exige al menos 12 caracteres con letras y números (requisito 8.3.6). Instala el módulo de calidad de contraseñas, que se activa automáticamente en PAM:
sudo apt install libpam-pwquality
Edita su configuración:
sudo nano /etc/security/pwquality.conf
Descomenta o añade estas líneas:
minlen = 12
dcredit = -1
lcredit = -1
dcredit = -1 y lcredit = -1 exigen al menos un dígito y una letra minúscula. Pruébalo cambiando tu contraseña con passwd: una contraseña corta se rechazará con BAD PASSWORD: The password is shorter than 12 characters.
Cerrar sesiones inactivas
Las sesiones inactivas más de 15 minutos deben exigir volver a autenticarse (requisito 8.2.8). Crea un archivo en /etc/profile.d/:
sudo nano /etc/profile.d/tmout.sh
TMOUT=900
readonly TMOUT
export TMOUT
Abre una sesión nueva y compruébalo con echo $TMOUT, que debe devolver 900.
Autenticación multifactor
PCI-DSS exige MFA para todo acceso al CDE (requisito 8.4.2). La forma más sencilla de cumplirlo sin tocar cada servidor es permitir SSH solo desde una VPN o un bastión que ya exija MFA, que es lo que hace la regla del paso 2 con your_admin_ip. Aplica MFA también al panel de administración de la tienda y a la cuenta de tu proveedor de hosting y de pagos.
Paso 5: Cifrar las comunicaciones con TLS 1.2 o superior (requisito 4)
Las versiones de SSL y TLS anteriores a 1.2 no están permitidas. En Ubuntu, el archivo principal de Nginx las incluye por defecto. Edítalo:
sudo nano /etc/nginx/nginx.conf
Busca la línea ssl_protocols en el bloque http y déjala así:
ssl_protocols TLSv1.2 TLSv1.3;
Valida y recarga:
sudo nginx -t
sudo systemctl reload nginx
Comprueba desde otra máquina las versiones aceptadas:
nmap --script ssl-enum-ciphers -p 443 your_domain
La salida solo debe mostrar TLSv1.2 y TLSv1.3. Si un bloque server define su propio ssl_protocols, ese valor tiene prioridad: búscalos con sudo grep -rn ssl_protocols /etc/nginx/.
Paso 6: Mantener el sistema parcheado (requisito 6.3.3)
Los parches de seguridad críticos deben instalarse en el plazo de un mes desde su publicación. En Ubuntu, unattended-upgrades instala las actualizaciones de seguridad a diario. Comprueba que está activo:
cat /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Si el archivo no existe o los valores son 0, actívalo:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Algunas actualizaciones, como las del kernel, solo se aplican tras reiniciar. Comprueba si hay un reinicio pendiente:
ls /var/run/reboot-required 2>/dev/null && cat /var/run/reboot-required.pkgs
Recuerda que esto no cubre la aplicación: el CMS de la tienda, sus plugins y las dependencias de PHP o Node.js se actualizan por separado y también cuentan para este requisito.
Paso 7: Proteger los scripts de la página de pago (requisitos 6.4.3 y 11.6.1)
Los ataques tipo Magecart inyectan JavaScript en la página de pago para copiar la tarjeta mientras el cliente la escribe. PCI-DSS 4.0 exige controlar qué scripts se cargan en esa página y detectar cambios no autorizados. Una cabecera Content Security Policy (CSP) limita los orígenes desde los que el navegador acepta scripts y marcos.
La política debe aplicarse a la página de pago sin tocar cómo tu aplicación procesa esa ruta. Para ello, define la cabecera con un map según la URL: Nginx no envía una cabecera cuyo valor es una cadena vacía, así que solo aparecerá en las rutas de pago. Crea el archivo en conf.d, que se carga dentro del bloque http. Sustituye /checkout por la ruta de pago de tu tienda y https://pagos.example.com por el dominio que indique la documentación de tu proveedor de pagos:
sudo nano /etc/nginx/conf.d/csp-pago.conf
map $request_uri $csp_pago {
~^/checkout "default-src 'self'; script-src 'self' https://pagos.example.com; frame-src https://pagos.example.com; object-src 'none'; base-uri 'self'";
default "";
}
Después añade la cabecera en el bloque server de la tienda:
sudo nano /etc/nginx/sites-available/your_domain
server {
server_name your_domain;
add_header Content-Security-Policy $csp_pago always;
# ... resto de la configuración del sitio
}
Notaen Nginx, un bloque
locationque define su propioadd_headerno hereda los del bloqueserver. Si lalocationque sirve el pago (por ejemplo, la de PHP) ya tiene alguna directivaadd_header, añade también ahí la línea de CSP.
Valida, recarga y comprueba la cabecera:
sudo nginx -t
sudo systemctl reload nginx
curl -sI https://your_domain/checkout | grep -i content-security-policy
Abre la página de pago en el navegador con las herramientas de desarrollo y revisa la consola: cada recurso bloqueado aparece como un error de CSP. Añade solo los orígenes que realmente necesites y mantén una lista documentada de cada script con su justificación, que es lo que pide el requisito 6.4.3.
Paso 8: Registrar y conservar los eventos (requisito 10)
PCI-DSS exige registrar el acceso de los usuarios, las acciones con privilegios y los cambios en los mecanismos de autenticación, sincronizar la hora y conservar los logs al menos 12 meses, con los 3 últimos disponibles de inmediato (requisito 10.5.1).
Comprueba que la hora está sincronizada (requisito 10.6):
timedatectl
Local time: Fri 2026-09-25 10:40:12 UTC
Universal time: Fri 2026-09-25 10:40:12 UTC
RTC time: Fri 2026-09-25 10:40:12
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
Instala auditd para registrar los cambios en cuentas, privilegios y configuración de SSH:
sudo apt install auditd
sudo nano /etc/audit/rules.d/pci.rules
-w /etc/passwd -p wa -k identidades
-w /etc/group -p wa -k identidades
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k ssh_config
-w /etc/ssh/sshd_config.d -p wa -k ssh_config
-w /var/www/your_domain -p wa -k web_cambios
Carga las reglas y compruébalo:
sudo augenrules --load
sudo auditctl -l
Los logs locales se pueden borrar si alguien compromete el servidor, y guardar 12 meses en el propio servidor tampoco es práctico. Reenvía los logs del sistema a un servidor de logs central por TCP:
sudo nano /etc/rsyslog.d/90-remoto.conf
*.* @@logs.example.com:514
sudo systemctl restart rsyslog
logger "prueba de reenvío PCI"
Comprueba en el servidor central que ha llegado el mensaje. La retención de 12 meses se configura allí.
Paso 9: Detectar cambios en archivos críticos (requisito 11.5.2)
Debes comparar los archivos críticos del sistema y de la aplicación al menos una vez por semana para detectar cambios no autorizados. AIDE crea una base de datos con las sumas de los archivos y avisa de cualquier diferencia. Instálalo:
sudo apt install aide
Genera la base de datos inicial. Tarda varios minutos:
sudo aideinit -y -f
Lanza una comprobación manual:
sudo aide --config /etc/aide/aide.conf --check
AIDE found NO differences between database and filesystem. Looks okay!!
Añade el directorio de la tienda a la configuración de AIDE para vigilar el código de la aplicación, y tras cada despliegue legítimo actualiza la base de datos con sudo aideinit -y -f para que solo aparezcan los cambios no previstos. El paquete de Ubuntu incluye además una comprobación diaria automática (dailyaidecheck).
Paso 10: Escanear las vulnerabilidades (requisito 11.3)
Estos escaneos no los puedes resolver solo desde el servidor, pero son obligatorios:
- Escaneo externo trimestral por un proveedor aprobado (ASV) y tras cambios significativos. Tu banco o proveedor de pagos suele ofrecerlo o recomendar uno.
- Escaneo interno trimestral de vulnerabilidades, corrigiendo las de riesgo alto y crítico.
Como comprobación de bastionado complementaria (no sustituye a un escaneo de vulnerabilidades), Lynis revisa la configuración del sistema y propone mejoras:
sudo apt install lynis
sudo lynis audit system
Al final muestra un índice de bastionado y una lista de sugerencias en /var/log/lynis-report.dat.
Resumen de la lista de verificación
| Requisito | Control | Cómo verificarlo |
|---|---|---|
| Alcance | Pago por redirección o iframe; sin CVV ni PAN en el servidor | grep de números de tarjeta sin resultados |
| 1 | Cortafuegos con SSH restringido | sudo ufw status numbered |
| 2 | Sin servicios ni credenciales por defecto | systemctl list-units --state=running |
| 4 | Solo TLS 1.2 y 1.3 | nmap --script ssl-enum-ciphers |
| 6.3.3 | Parches de seguridad automáticos | /etc/apt/apt.conf.d/20auto-upgrades |
| 6.4.3 / 11.6.1 | CSP e inventario de scripts en la página de pago | curl -sI a la página de pago |
| 8 | SSH por clave, cuentas individuales, MFA, contraseñas de 12 caracteres, TMOUT | sudo sshd -T |
| 10 | auditd, hora sincronizada, logs centralizados 12 meses | sudo auditctl -l, timedatectl |
| 11.5.2 | Detección de cambios con AIDE | sudo aide --check |
| 11.3 | Escaneos trimestrales interno y ASV | Informes del escaneo |
Conclusión
La forma más eficaz de cumplir PCI-DSS en una tienda online es que la tarjeta nunca toque tu servidor; a partir de ahí, has aplicado los controles que dependen del sistema: cortafuegos, acceso administrativo, TLS, parches, protección de la página de pago, registros y detección de cambios. Como siguientes pasos, guarda la evidencia de cada verificación con su fecha, programa las revisiones periódicas (cortafuegos cada seis meses, escaneos cada trimestre) y confirma con tu banco adquirente qué cuestionario te corresponde.
