# DirectAdmin

Directadmin es un panel de control para hosting. Tiene protección de precios, para no hacer lo que están haciendo los propietarios de cPanel, WHMCS, subiendo los precios día si, día también.  
"Protection against arbitrary price increases only. Does not apply to inflation, extraordinary world events, currency devaluation, etc. Does not apply to add-ons given as a free gift/bonus."  
[Lista del Canal YouTUbe :: Directadmin](https://www.youtube.com/playlist?list=PLwMKHekrvzePpz9CQ0Z65bsu0wdwlkG-f "Lista del Canal TecnoBoomer - Directadmin")

# Your IP is blacklisted - DirectAdmin

# Introducción
No siempre es el firewall quein nos bloquea. Ademas en este caso el mensaje es recibido en el navegador, que es un asunto diferente a un bloqueo (baneo por firewall) que no permite noi alcanzar la página de acceso (login)

## Sintoma
Al intentar acceder a la página de acceso (login) recibimos el mensaje:
```
Your IP is blacklisted
```

## Solución
La Ip estará en el fichero de la lista negra (blacklist) y debemos eliminarlo de alli entrando por la consola SSH (shell) y deberemos eliminarlo con nuestr editor favorito

```
/usr/local/directadmin/data/admin/ip_blacklist
```

## IP fijas añadidas a la lista blanca (whitelist)
Tambien podemos añadir la IP de nuestra ip se esta es fija, añadiendo una IP por linea.
> JAMAS debemos añadir IP dinámicas, a una sistema de listas blancas. Es el camino a ir haciendo menos seguro nuestro sistema.
```
/usr/local/directadmin/data/admin/ip_whitelist
```

## Información adicional
La configruación de esta propiedad de DirectAdmin se encuentra en 
Admin Level -> Admin Settings -> Blacklist IPs for excessive login attempts

- [Your IP is blacklisted](https://help.directadmin.com/item.php?id=306)

# Imapsync

Guía para migrar buzones de correo entre dos servidores IMAP usando `imapsync` **desde la línea de comandos**, no desde el botón del panel. Por consola ves el progreso buzón a buzón, puedes hacer una prueba en seco antes de tocar nada, y si algo falla te dice exactamente qué. Sirve para cualquier migración entre dos servidores con IMAP activo (cPanel, DirectAdmin, Plesk, correo genérico), independientemente del panel de cada lado.

La idea en una frase: `imapsync` se conecta a los **dos** servidores a la vez por IMAP, compara los dos buzones y **copia del origen al destino solo lo que falta**. Es **incremental**: lo puedes ejecutar las veces que quieras, no duplica correos.

## Instalación

En muchas distribuciones Debian/Ubuntu recientes `imapsync` está empaquetado directamente:

```bash
apt install -y imapsync
```

Si no está en los repositorios, se instalan sus dependencias Perl y se ejecuta desde el fuente:

```bash
apt install -y libencode-imaputf7-perl \
 libfile-copy-recursive-perl \
 libio-tee-perl \
 libmail-imapclient-perl \
 libunicode-string-perl \
 libreadonly-perl \
 libsys-meminfo-perl \
 libregexp-common-perl \
 libfile-tail-perl \
 libdata-uniqid-perl \
 libjson-webtoken-perl \
 libtest-mockobject-perl
```

En AlmaLinux/RHEL, habilitar EPEL (`dnf install epel-release`) e instalar las dependencias Perl equivalentes. La lista completa y actualizada por distribución está en la web oficial del proyecto: https://imapsync.lamiral.info

## Concepto clave: imapsync NO usa el MX del dominio

Este es el punto que más confusión genera. `imapsync` no mira el registro MX del dominio para nada. Se conecta **directo por IMAP a cada servidor, por su hostname**:

- **Origen (`--host1`):** el hostname del servidor viejo, IMAP SSL, puerto 993.
- **Destino (`--host2`):** el hostname del servidor nuevo, IMAP SSL, puerto 993.

Por eso se puede sincronizar **antes** de tocar el DNS: mientras el MX del dominio siga apuntando al servidor viejo, el correo nuevo sigue entrando en el origen con total normalidad, y se va copiando por detrás sin que el usuario note nada. Esto es lo que permite hacer la migración en dos tiempos (ver «Pasada final» más abajo).

## Dónde encaja imapsync en una migración completa

`imapsync` copia el **contenido** de los buzones, pero **no crea nada** (ni el dominio, ni las cuentas de correo). Cuando no hay migrador automático entre paneles, el orden por cada dominio es:

1. Crear el dominio en el servidor destino.
2. Crear las cuentas de correo (buzones) del dominio, cada una con su contraseña.
3. Primera pasada de `imapsync` (mueve el grueso del correo).
4. Pasada final de `imapsync`, justo antes de cambiar el DNS.
5. Cambiar el DNS/MX del dominio al servidor destino.

## Requisitos previos

- **El buzón destino tiene que existir ya en el servidor nuevo.** `imapsync` copia el contenido, pero no crea la cuenta. Primero se crea el dominio y el buzón (con su contraseña nueva), y luego se sincroniza.
- **Acceso SSH al servidor donde se lanza `imapsync`** (normalmente el destino).
- **`imapsync` instalado en uno de los dos lados** (basta con el que lanza los comandos). El otro servidor no necesita nada especial: solo tener IMAP activo (puerto 993 SSL).

## Los datos que necesitas por cada buzón

`imapsync` conecta a los dos lados, así que por cada buzón necesitas cuatro cosas:

- **Usuario origen** = la dirección completa, p. ej. `usuario@dominio.tld`
- **Contraseña origen** = la que ese buzón tiene **ahora** en el servidor viejo
- **Usuario destino** = la misma dirección, `usuario@dominio.tld`
- **Contraseña destino** = la que se le puso al crear el buzón en el servidor nuevo

Y los dos servidores (fijos para toda la migración):

- **Origen (`--host1`):** `<HOST_ORIGEN>` (IMAP SSL, puerto 993)
- **Destino (`--host2`):** `<HOST_DESTINO>` (IMAP SSL, puerto 993)

## Paso 1 — Entrar por SSH al servidor

```bash
ssh -p <PUERTO_SSH> <USUARIO>@<HOST_DESTINO>
```

## Paso 2 — Guardar las contraseñas en ficheros (no ponerlas en el comando)

Si la contraseña se escribe dentro del comando, queda registrada en el historial y cualquiera que mire los procesos del servidor la ve. Es mejor meterlas en dos ficheros sueltos:

```bash
cd ~
umask 077                     # los ficheros que se creen a partir de ahora nacen privados
echo 'CONTRASENA_ORIGEN'  > .p1
echo 'CONTRASENA_DESTINO' > .p2
```

Estos dos ficheros se reutilizan/reescriben para cada buzón. Al terminar la migración, se borran (`rm ~/.p1 ~/.p2`).

## Paso 3 — Prueba en seco (SIEMPRE primero)

El `--dry-run` **no copia nada**: solo se conecta a los dos lados y dice qué haría. Confirma que las contraseñas son correctas y muestra cuántos correos hay que mover:

```bash
imapsync \
  --host1 <HOST_ORIGEN>  --user1 usuario@dominio.tld --passfile1 ~/.p1 --ssl1 \
  --host2 <HOST_DESTINO> --user2 usuario@dominio.tld --passfile2 ~/.p2 --ssl2 \
  --automap \
  --dry-run
```

Si al final aparece un resumen con las carpetas y un recuento de mensajes, y **sin errores de login**, ya está todo correcto para la copia real.

## Paso 4 — La copia real

Es exactamente el mismo comando **quitando** `--dry-run`:

```bash
imapsync \
  --host1 <HOST_ORIGEN>  --user1 usuario@dominio.tld --passfile1 ~/.p1 --ssl1 \
  --host2 <HOST_DESTINO> --user2 usuario@dominio.tld --passfile2 ~/.p2 --ssl2 \
  --automap
```

Los correos pasan carpeta por carpeta. Al final imprime un resumen. Líneas a mirar:

- **`Host2 messages after sync`** — cuántos mensajes hay en el buzón nuevo al terminar.
- **`messages transferred`** — cuántos ha copiado en esta pasada.
- **`Detected 0 errors`** — lo que se quiere ver. Si hay errores, aparecen listados arriba.

## Paso 5 — Pasada final antes de cambiar el DNS

Mientras no se cambie el MX/DNS del dominio, el correo nuevo **sigue llegando al servidor viejo**. Por eso se hace en dos tiempos:

1. Una **primera pasada** (mueve el grueso; puede tardar si el buzón es grande).
2. Una **pasada final** justo cuando se vaya a cambiar el DNS del dominio al servidor nuevo: se **vuelve a lanzar el mismo comando** y solo trae los correos que hayan entrado desde la primera vez. Como es incremental, es rápida y no duplica nada.

## Buzones POP

- `imapsync` copia **lo que esté guardado en el servidor origen**. Da igual que el usuario final use POP en su cliente de correo: el servidor sigue teniendo el buzón por dentro e `imapsync` entra por IMAP a leerlo.
- **Pero** si ese usuario tiene el POP configurado como *«borrar del servidor al descargar»*, en el origen casi no queda nada (el correo está en su equipo, no en el servidor). En ese caso `imapsync` copiará poco, y es lo correcto: lo que está en el equipo del usuario no se puede migrar desde el servidor, se queda en su equipo (ni se pierde ni se toca).

## Migrar varios buzones de una vez

Cuando se coge ritmo, en lugar de ir uno a uno se puede preparar un fichero con todos los buzones y un bucle. Se crea `~/buzones.csv` con una línea por buzón, separado por punto y coma:

```text
usuario@dominio.tld;PASS_ORIGEN;PASS_DESTINO
otro@dominio.tld;claveVieja2;claveNueva2
```

Y se procesa con este bucle:

```bash
cd ~
umask 077
while IFS=';' read -r BUZON P1 P2; do
  [ -z "$BUZON" ] && continue
  echo "===== Migrando $BUZON ====="
  printf '%s' "$P1" > .p1
  printf '%s' "$P2" > .p2
  imapsync \
    --host1 <HOST_ORIGEN>  --user1 "$BUZON" --passfile1 ~/.p1 --ssl1 \
    --host2 <HOST_DESTINO> --user2 "$BUZON" --passfile2 ~/.p2 --ssl2 \
    --automap
done < ~/buzones.csv
rm -f ~/.p1 ~/.p2
```

Para probar el lote entero en seco antes, se añade `--dry-run` a la línea de `imapsync` dentro del bucle. Al terminar, se borra el CSV con las contraseñas: `rm ~/buzones.csv`.

## Chuleta de opciones útiles

| Opción | Para qué sirve |
|--------|----------------|
| `--dry-run` | Simula sin copiar nada. Usar siempre la primera vez. |
| `--automap` | Empareja las carpetas especiales (Enviados, Papelera, Spam) aunque se llamen distinto en cada servidor. |
| `--justfolders` | Crea solo la estructura de carpetas, sin mensajes. |
| `--folder "INBOX"` | Migra solo una carpeta concreta. |
| `--exclude "Trash\|Junk"` | Excluye carpetas (p. ej. no traer Papelera ni Spam). |
| `--maxage 365` | Solo correos de los últimos N días. |
| `--useheader "Message-Id"` | Evita duplicados comparando por identificador de mensaje (útil al repetir la sync). |
| `--sslargs1 SSL_verify_mode=0` | Ignora la validación del certificado SSL del origen (ver «Si algo falla»). |

**No usar nunca** `--delete2` (borra en el destino) ni `--delete1` (borra en el origen) en una migración normal: no hacen falta y son peligrosos.

## Si algo falla

- **`Error login on host1` / `host2`** → contraseña incorrecta en ese lado. Revisar el fichero `.p1` (origen) o `.p2` (destino).
- **Error de certificado SSL** → añadir al comando `--sslargs1 SSL_verify_mode=0` y/o `--sslargs2 SSL_verify_mode=0`.
- **`Host2 folder ... does not exist`** → normalmente se resuelve con `--automap`.
- **Cualquier otra cosa rara** → conservar el resumen final (las últimas ~30 líneas que imprime `imapsync`) para diagnóstico. Ese resumen es justo la ventaja de hacerlo por consola: se tiene todo delante.

# Upgrade de MySQL 5.7 a MySQL 8.0 - DirectAdmin

## Introducción
Un cambio muy necesario (por más que se empeñen algunos) es la actualización a MySQL 8.0. Si seguimos interesados por seguir trabajando con MySQL, es necesario, ya que los cambios, la seguridad, y la eficacia del motor MySQL en la 8.0 supera con creces  al viejo motor 5.7

Pero una de las cosas que nos trajo, las distintas bifurcaciones (forks) de MySQl (Percona, MariaDB, y otros) es que ya no son tan compatibles, o cuando menos existen ciertos cambios que nos traen de cabeza a los administradores de sistemas, sobre todo cuando nos centramos en el trabajo como desarrolladores. 

> Muchos desarrolladores no conocen en profundidad los sistemas, y eseo es fuente de problemas de mantenimiento y despliegue. 

### Escenario
El escenario de este artículo es:


### Documentación original
La documentación original para actualizar MySQl 5.7 a MySQL 8.0 con Custom Builds, es 
[How to upgrade mysql with custombuild 2.0](https://help.directadmin.com/item.php?id=240)

```
cd /usr/local/directadmin/custombuild
./build set mysql 8.0
./build set mysql_inst mysql
./build set mysql_backup yes
./build update
./build mysql
```

Mención importante es hacer un backup antes de nada, o bien tener la opción `mysql_backup=yes` en el fichero `/usr/local/directadmin/custombuild/options.conf` mediante el seteo `./build set mysql_backup yes`

### Posibles problemas y sus soluciones
Todo debería ir bien pero siempre podemos encontrarnos con algo que se escapo de las manos. Algo que alguien toco, algo que tocamos, algo modificado, que ha dejado la actualización con problemas de permisos, uno de los handicaps de este cambio junto con el de las variables de entorno de MySQL, relativas a ciertos cambios respecto de los valores por defecto, nulos, y otras cuestiones.

#### No funciona webmail ni PhpMyAdmin
Cuando pasa esto, no te lo esperas. Primero porque ves que puedes entrar perfectamente al panel de DirectAdmin de tu servidor, pero sin embargo te falla el acceso a estos sitios tan importantes.

El que ocurra esto es una pista muy importante porque demuestra que la autenticación está correcta, ya que ves el panel de control. Así pues, el problema está en algo que afecta a estas dos piezas.

Lo normal es que acudas a la reconstrucción (rebuild) de ambos elementos, pero eso no es sino una pérdida de tiempo, salvo que leamos un cierto detalle si hacemos caso de un tip habitual de DirectAdmin que pasa por la eliminación de la base de datos de rouncube y la instalación desde 0 de roundcube
```
mysql> DROP DATABASE da_roundcube;
Query OK, 15 rows affected (0.08 sec)


mysql> exit
Bye
root@server-xerintel01:/usr/local/directadmin/custombuild# ./build roundcube
Inserting data to mysql and creating database/user for roundcube...
Found MySQL version 8.0
Granting access: GRANT SELECT,INSERT,UPDATE,DELETE,CREATE,DROP,ALTER,LOCK TABLES,INDEX,REFERENCES ON da_roundcube.* TO 'da_roundcube'@'localhost';
Setting password: ALTER USER 'da_roundcube'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yuCf7CImtO5';
Database created, da_roundcube password is yuCf7CImtO5
Editing roundcube configuration...
Roundcube 1.4.11 has been installed successfully.
WARNING: Changed defaults (These config options have new default values):
- 'skin'
- 'smtp_port'
- 'smtp_user'
- 'smtp_pass'
- 'jquery_ui_skin_map'
Executing database schema update.
ERROR: SQLSTATE[HY000] [2054] Server sent charset unknown to the client. Please, report to the developers
ERROR: Failed to connect to database
```


El problema está en el juego de caracteres () por defecto no compatible así que mejor lo cambiamos en el fichero `/etc/my.cnf` y hacemos un restart del servidor mysqld.

``` 
[mysqld]
…
# character-set-server=utf8mb4
character-set-server=utf8
```

Una vez reiniciado nuestro servidor mysql, tendremos tanto Roundcube como PHPMyAdmin operativos.


#### Otros problemas derivados de las variables de entorno
Es muy típico en estos upgrades o en los de MariaDB, encontrarnos con problemas derivados de los cambios de tratamiento de ciertos aspectos de estos servidores Bases de Datos, llamados **Server SQL Modes** y documentados de forma oficial en [5.1.11 Server SQL Modes](https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html)

Son muy comunes:
Los datos inválidos que antaño se soportaban en MySQL, convirtiendose en ‘0000-00-00’
Los ANSI_QUOTES
El error de división por 0 

Las soluciones para muchos programadores como norma general, serán la vía rápida y la vía rapidísima. 

##### Vía rapidísima
Tras una búsqueda en Google, DuckduckGo que les llevará lectura en stackoverflow, haran un copy & paste y desactivaran TODO lo que ha avanzado MySQL en la protección y homogeneización del desarrollo de bases de datos SQL, y por supuesto de la optimización.

Es decir, desactivaran todo lo que dice el post, y como les funciona, pues ya está. Hasta otro día en el que por una migración, desastre, o lo que sea que haya modificado el comportamiento de MySQL, se repita. Por ende, ni siquiera estará anotado en los requerimientos del software, y con toda seguridad será un quebradero de cabeza para el administrador de sistemas que lidie con ello.

##### Vía rápida
En la misma búsqueda, vemos el tema, y tratamos de analizarlo. Bien, somos conscientes del problema, y buscamos las variables adecuadas al problema que deberemos cambiar.

Por ejemplo, tenemos problemas con datos nulos o incorrectos en un datetime.

```
“Incorrect date value: ‘0000-00-00’ for column ‘datsent’ at row 1”
```

La solución rápida pasaría por modificar los modos SQL a **NO_ZERO_IN_DATE,NO_ZERO_DATE** 

Lo primero es guardar la configuración actual

```
mysql> SELECT @@GLOBAL.sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@GLOBAL.sql_mode                                                                                                     |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
```
Lo segundo es removerlo los modos relativos al problema en tiempo de ejecución

```
mysql>  SET GLOBAL sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
mysql>  SET SESSION sql_mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
```

Lo tercero es añadirlo al fichero my.cnf para que en el siguiente reinicio los valores esten de la forma deseada.

```
[mysqld]
sql_modes=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
```

Por supuesto añadir a la documentación del proyecto la necesidad de que conservemos o tengamos esos modelos de SQL

##### Vía correcta
Lamentablemente los anteriores caminos no son sino un parche que tarde o temprano nos pasará factura, además de que suponen una mala praxis profesional, ya que la diferencia entre hacer las cosas bien y no hacerlas bien, es también tiempo, y el tiempo es dinero. 

**¿Qué es pues lo correcto?**

Lo correcto es modificar lo que está incorrecto, que muchas veces no es mucho más complicado que una o dos horas de un programador junior.

No vamos a explicar el cómo, detalladamente pues las variantes son muchas, pero básicamente diremos que debemos actualizar las filas actuales de fechas a 0 a valores válidos (‘1970-01-02’  es un valor válido) o bien permitir que la columna tenga valores nulos.


#### Nota adicional sobre backups

Un caso muy doloroso, es cuando por razones de urgencia debemos migrar o restaurar nuestra base de datos en otro sistema (ejemplo, un incendio en nuestro centro de datos de OVH).

La solución pasa por una modificación temporal de los modos sql. 

[Error 1292 y Error 1217](https://castris.com/mysql-restore-con-error-1217-y-error-1292/) fue un artículo que escribe hace tiempo a colofón de estos problemas

La cuestión es añadir al principio de nuestro fichero sql 
```
/* Añadir al principio */
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS;
SET FOREIGN_KEY_CHECKS=0;
-- save current setting of sql_mode
SET @old_sql_mode := @@sql_mode ;

-- derive a new value by removing NO_ZERO_DATE and NO_ZERO_IN_DATE
SET @new_sql_mode := @old_sql_mode ;
SET @new_sql_mode := TRIM(BOTH ',' FROM REPLACE(CONCAT(',',@new_sql_mode,','),',NO_ZERO_DATE,'  ,','));
SET @new_sql_mode := TRIM(BOTH ',' FROM REPLACE(CONCAT(',',@new_sql_mode,','),',NO_ZERO_IN_DATE,',','));
SET @@sql_mode := @new_sql_mode ;
```
Y al final
```
echo 'SET @@sql_mode := @old_sql_mode; ' >> dump.sql
echo ’SET @@FOREIGN_KEY_CHECKS := @OLD_FOREIGN_KEY_CHECKS; ‘>> dump.sql
```

#### Enlaces 
- [Upgrade mysql from 5.7 to Mysql 8.0 some issues](https://forum.directadmin.com/threads/upgrade-mysql-from-5-7-to-mysql-8-0-some-issues.63771/)
- [How to set sql_mode in my.cnf in MySQL 8?](https://stackoverflow.com/questions/54007824/how-to-set-sql-mode-in-my-cnf-in-mysql-8)

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como esta, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/cart.php?gid=18).

# Tips de Directadmin

# Como ejecutar tu acceso a mysql en un Directadmin sin usar root

## Introducción Mysql password en Directadmin

Muchos estamos habituados al uso de un `.my.cnf` en nuestro usuario para acceder como `root`a mysql.

En el caso de **Directadmin** la instalación por defecto no autroiza a root con el uso de sockets, si no que es una instalación personalizada, en la que el usuario que tiene privilegios globales se llama `da_admin` y tiene su contraseña guardada en `/usr/local/directadmin/conf/mysql.conf`

Si no queremos añadir otro usuario y conservar este sistema, podemos usar este comando (o generar un alias para usarlo ;-) ) 

```bash
mysql -u da_admin -p$(grep 'passwd' /usr/local/directadmin/conf/mysql.conf | awk -F= '{print $2}')
```

### Alternativa .my.cnf

La otra es la de siempre, si confias en ella que en realidad es mas segura que pasar un password por un script, que es editar el fichero `/root/.my.cnf`

```bash
[client]
user=da_admin
password=PasswordObtenidodelFecijeroDeConfiguracion
```

> En ese caso tendras que podres hacer `sudo mysql xxxx`

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Desactivar el acceso público a PhpMyAdmin en Directadmin

## Introducción

Nunca me ha gustado tener phpMyAdmin activo en mis servidores, pero es algo inevitable. Lo usuarios no tiene porque saber acceder a mysql para hacer ciertas cosas, y además el 99% de los super tutoriales para hacer cosita estan basados en esa herramienta. 

Pero eso sin, lo que no nunca permito es el acceso directamente. Al mnso que el phpMyAdmin goce de la capa de protección adicional de estar logeado en el cpanel.

Eso supone que si tienes un desarrollador web, que lo necsita y no le quieres dar acceso, un par de cosas:

- Un desarrollador de verdad, no necesita acceso a phpMyadmin
- Un desarrllador de verdad, no necesita acceso remoto via 3306 (puerto de mysql el cual tambien suelo capar) sino acceso SSH para acceder via tunel

> Si de verdad necesitas trabajar como desarrllador sobre mysql te recomiendo usar [Mysql sobre SSH](https://manuales.castris.com/mysql-remote/)


### Desactivar el acces público de phpMyAdmin en Directadmin
Versión en el momento de escribir el artículo: 1.666

[Quiero que todo el acceso a /phpMyAdmin sea accesible solo a través de DirectAdmin.](https://docs.directadmin.com/other-hosting-services/mariadb-mysql/phpmyadmin.html#i-want-all-access-to-phpmyadmin-only-be-accessible-through-directadmin)

```bash
da build set phpmyadmin_public no
da build phpmyadmin
```

### Incio de sessión SignOn en PhpMyAdmin

[Inicio de sesión con un solo clic (Single SignOn) a PHPMyAdmin](https://docs.directadmin.com/other-hosting-services/mariadb-mysql/phpmyadmin.html#one-click-single-signon-login-to-phpmyadmin)



##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Tips rápidos de Directadmin

## Introduccción
Algunas veces las cosas son difíciles por la confusión generada en la propia documentación de lo que usamos. 

Y esto puede llevarnos a la desesperación.

Aquí te dejo trucos y si tienen modificación tras alguna nueva versión con su versión desde la cuál se comprobó. El documento se empezó en julio de 2024 con la [versión 1.6.6](https://docs.directadmin.com/changelog/version-1.666.html)

> Lo pongo en inglés el tablero porqué jamás uso el español como idioma en el software, ya que los mejores manuales y tutoriales esta en el idioma de **Sakespeare** 😉.

Después de cualquier modificación que queramos que sea aplicada debemos hacer un restart de DirectAdmin `systemctl restart directadmin`

**Última actualizacion: 7/08/2024 18:00 GMT +2**

## Índice
- [Apuntes sobre Let's Encrypt For Services en la documentación oficial de DirectAdmin](#bkmrk-apuntes-sobre-let%27s-)
- [Actualizar la quota de una cuenta de correo](#bkmrk-actualizar-la-quota-)
- [Cambia el email del admin](#bkmrk-cambia-el-email-del-)
- [Cambio del hostname](#bkmrk-cambio-del-hostname-)
- [Cambia valores de configuración de Directadmin en el shell](#bkmrk-cambia-valores-de-co)
- [Cloudflare para el tablero o panel de control Directadmin](#bkmrk-cloudflare-para-el-t)
- [Composer en Directadmin](#bkmrk-composer-en-directad)
- [Let's encript para dominios que han fallado](#bkmrk-let%27s-encrypt-para-d)
- [Valores por usuario en el user.conf](#bkmrk-valores-por-usuario-)
- [Webmail impersionation - OneClick](#bkmrk-webmail-impresionati)
- [directadmin license-expired](#bkmrk-directadmin-license-)
  

### Apuntes sobre Let's Encrypt For Services en la documentación oficial de DirectAdmin

[Let's Encrypt For Services](https://docs.directadmin.com/webservices/ssl/service-ssls-and-le.html#let-s-encrypt-for-services)

### Actualizar la quota de una cuenta de correo

Muchas veces el usuario cambia o purga el contenido de sus cuentas de correo y llos cambios no se ven reflejados inmediatamente en Directadmin. 

Esto es frustrante para el usuario y para el técnico de soporte.

Las cuotas no son algo instantaneo, y se ejecutana cada cierto tiempo, y son globales, lo que hace que el tiempo es largo. Ademas de que generalmente esas cosas se hacen de noche y observando algunas pautas apara evitar sobrecargas.

Podemos actualizar los datos de un cliente con este comando de Devecot [doveadm quota recalc](https://doc.dovecot.org/configuration_manual/quota_plugin/#quota-recalculation)

```bash
doveadm quota recalc -u user@domain.ltd
```

Después podemos verificar con:

```bash
doveadm quota get -u user@domain.ltd
Quota name Type    Value Limit                                                                                           %
           STORAGE     0     -                                                                                           0
           MESSAGE     0     -                                                                                           0
```

### Cambia el email del admin

Vía rápida por SSH

Editando el fichero `/usr/local/directadmin/data/users/admin/user.conf` la variable `email=` y haciendo un restart de `directadmin`

```bash
systemctl restart directadmin
```

> Igual tienes que revisar el CSF para las notificaciones

Vía tablero en `Dashboard > User Profile > General`

- [Change admin email?](https://forum.directadmin.com/threads/change-admin-email.59943/)

### Cambio del hostname y forzar certificado de hostname

En principio uno pensaría que con cambiar el **hostname** en el `Tablero > Admin > Server Manager > Administrator Settings > Server Settings` se producirían todas las acciones necesarias.

Pues no. Y además, si buscas, puede que lo encuentres a la primera pero puede que comiences un viaje a ninguna parte. [Hostname change does not work properly.](https://forum.directadmin.com/threads/hostname-change-does-not-work-properly.71540/)


Aquí te lo dejo mas formalito, y se entiende que `hostname -f` te resuelve el Hostname que tu quieres y tienes ya configurado para resuelva a tu servidor.

```
/usr/local/directadmin/scripts/letsencrypt.sh server_cert `hostname -f`
```

### Cambia valores de configuración de DirectAdmin en el shell

En la documentación tenemos [todos los valores de configuración de Directadmin](https://docs.directadmin.com/directadmin/general-usage/all-directadmin-conf-values.html#all-directadmin-conf-values) que puedes manejar en el shell.

Algunos de los son muchísimo más prácticos que ir buscando por su tablero de mandos.

Se cambian con:

```bash
da config-set variable value
systemctl restart directadmin
```

O también con 

```bash
/usr/local/directadmin/directadmin config-set variable value
systemctl restart directadmin
``` 

Si quieres buscar alguno o un grupo por palabra que buenbo usar `grep`

```bash
cat /usr/local/directadmin/conf/directadmin.conf | grep dkim
dkim=2
```

Hay variables que no funcionan con  `da config-set` y hay que editarlas manualment.

> A veces es mucho más práctico conocer la variable que ir buscando por el tablero

### Cloudflare para el tablero o panel de control

Interesante usar el tablero en otro puerto (por imperativo de Cloudflare) pero muy interesante para evitar un buen porcentaje de bobos haciendo pruebas contra tu panel.

Habilita el modo Cloudflare en tu registro que apunta a tu máquina, y comienza a funciona por el puerto 2096

```bash
da config-set port 2096
❯ systemctl restart directadmin
```
 
> Si tras hacer el cambio, tu panel de control Directadmin hace **parpadeo** (blinking,) elimina en las **herramientas del desarrollador** (Webmaster Tools), todo lo que hay en **Almacenamiento** y vuelve a hacer login.
>

### Composer en Directadmin

Por defecto no esta instalado, **composer v2** en **Directadmin**

```bash
da build composer
```


### Let's Encrypt para dominios que han fallado al crearse (SSL)

A veces puede darse que falle la creación de los certificados de un dominio. Meor que perder el tiempo en el tablero lo podemos solventar via terminal

```bash
domain=domain.tld
/usr/local/directadmin/scripts/letsencrypt.sh request $domain 4096
```

1. Entendemos `domain.tld` como un fake que debemos sustituir.
2. El dominio ya resuelve de forma global a nuestra máquina.

[Let's Encrypt For Domains](https://docs.directadmin.com/webservices/ssl/ssl-and-letsencrypt-for-domains.html)

### Valores por usuario en el `user.conf`

Muchas veces es posible que deseemos no estar tan limitados a los valores globales o aplicados por cuestión de un plan, o de los valores del usuario en la administración. Incluso hay valores que no están reflejados en el panel de administración, como puede ser el caso de la limitación del número de correos por usuario.

En este caso, entra en funcionamiento el `override` o `sobrescritura` de los valores de configuración, que podemos ejecutar ya sea con la edición del fichero `/usr/local/directadmin/data/users/<USER>/user.conf` o con el uso de la API.

#### Cambio del `max_per_email_send_limit` por usuario

Un ejemplo es el de cambiar el valor máximo que limita el envío de correos por día, algo muy útil en el entorno de hosting para evitar entrar en listas de spam, ya sea porque han hackeado una cuenta, un script abierto sin protección, etc.

En este caso, para permitir a un usuario saltarse el límite para una cuenta en concreto (atención, él podrá aplicar esto a todas sus cuentas, y lo lamento, pero hay mucho espabilado, así que tendréis que tener un mecanismo de vigilancia de su uso).

El cambio de forma global autorizaría a todos los usuarios al uso del máximo, en todas las cuentas configuradas como máximo en ese valor. Sin embargo, si lo hacemos modificando el fichero `/usr/local/directadmin/data/users/<USER>/user.conf` añadiendo `max_per_email_send_limit=VALUE` y haciendo un restart del servicio de **Directadmin**, el usuario podría en todas sus cuentas añadir dicho límite.

#### Webmail impresionation o OneClick

Para administradores y resellers, se puede realziar un impersonation, o como llaman en Directadmin, OneClik, para que el administrador puede acceder al roundcube. Información adicional: [One-Click login to RoundCube](https://docs.directadmin.com/other-hosting-services/webmail/general.html#one-click-login-to-roundcube)

```bash
da config-set one_click_webmail_login 1
systemctl restart directadmin
da build dovecot_conf
da build exim_conf
da build roundcube
```

> Sobre la discusión de la legalidad de esta accion, muchos paracen olvidar que el hoster, ya en su contrato (si se hizo correctamentre) es el responsable de la custodia de los datos, y por la naturaleza de sus credenciales, esta habilitado a ver todo. Si bien no de forma directa, si puede verlo desde el shell. Asi pues, en ese contrato, de responsabilidad, queda implícito que existe un total acceso a los datos de los clientes, suscrito a la profesionalidad y la confidencialidad, limitandose esos accesos a los estrictamente necesarios, para el buen funcionmiento del servicio.

#### directadmin license-expired

Aqui hablamos de un escenario, en el que en realidad el problema estaba relacionado con una sobre carga de tareas del **Directadmin** que se pueden ver con `journalctl -u directadmin`, pero esta página del manual de directadmin es muy interesante.

[License expired](https://docs.directadmin.com/directadmin/general-usage/licensing.html#license-expired)
 sobre todo, por que encontraremos el comando para refrescar la licencia en [Permanent license error 'invalid license key'](https://docs.directadmin.com/directadmin/general-usage/licensing.html#permanent-license-error-invalid-license-key)

  Asi que en realidad, aqui tenemos información para muchas situaciones diferentes que afectan al sistema de licencia de Directadmin.

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# ModSecurity en DirectAdmin

## Introducción

El uso de ModSecurity esta altamente recomendado. El software eactual, y mas con la proliferación de **expertos** en javascript o creadores de **temas** y **plugins** hacen más que necesaria una capa adicional de seguridad en la llamadas al servidor web.

###  ModSecurity en Directadmin con owasp

En mi caso opto siempre por owasp.

[ModSecurity Documentación para Directadmin](https://docs.directadmin.com/webservices/apache/modsecurity.html)

Algunos tips.

- Al principio es una pesadilla, pero lo mejor es no desactivar pro defecto. Es lo que hacen el 99% de los hosters que **no quieren lios** con sus clientes, pero luego sus clientes mueren una y otra vez de hackeos, inyecciones de código malicioso, y problemas de seguridad.
- Si alguna **regla** (rule ID) la tienes clara y documentada, puede desactivarla en el manager de ModSecurity que esta accesible en `/evo/admin/modsecurity`
- Esas reglas son globales, es decir las desactivas para todo el mundo, asi que ten muy claro que las desactivas por es necesario.

> Muchas reglas son necesarias para determinado templates famosos, crm y plugins que son famosos, y pese a ello, una autentica verguenza de codificación. Queda en tú política el permitirlo de forma global o obligar al usuario a que el actualice sus reglas haciendose responsable de su seguridad.


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# DKIM, SPF y DMARC para el hostname de un servidor con Directadmin

## Introducción

La importancia de tener al día y configurado un servidor y su **hostname** es crucial, para que el correo emitido por el servidor como tal (alertas, mensjaes al usuario, etc) y la fiabilidad del propio servidor es crucial.

Por el ello uno de los grandes olvidados es tener el SPF, DKIM, y DMARC para el `hostname` 


### Proceso de creación de los reegistros SPF, DKIM y DMARC en un host con Directadmin

#### Creación de la zona

Con indipendencia de si el hostname esta controlado por un dominio cuyo sistema de resolución es distinto al de la maquina, ya sea por DNS locales o por un cluster externo, o por un proveedor externo como Cloudflare, debemos crear una zona para el hostanme en el tablero.

#### SPF

Si lo tienes correctamente configurado (te aconsejo que pases por la documentación [All Directadmin Conf Values](https://docs.directadmin.com/directadmin/general-usage/all-directadmin-conf-values.html) ya que si esta configurado el lo creara automaticamente

> Si lo configuras tambien puedes modificar los valores por defecto, muy útil cuando tenemos servidores de correo de relay, etc.

Para ello además de la configuración hay que crear un fichero  `/usr/local/directadmin/data/templates/custom/dns_txt.conf`

```bash
|DOMAIN|.="v=spf1 a mx ip4:|SERVER_IP||EXTRA_SPF||SPF_IPV6| -all"
```

#### DKIM

EL DKIM lo podremos crear ahora que ya existe la zona, con:

```bash
 /usr/local/directadmin/scripts/dkim_create.sh `hostname -f`
```

Esto generará la entrada adecuada en el fichero de zona del hostname.


#### DMARC

Procederemos a editar el fichero anterior `/usr/local/directadmin/data/templates/custom/dns_txt.conf` para **añadir** el registro `_dmarc`

```bash
_dmarc="v=DMARC1; p=none; sp=none;"
```

Ahora nuestro fichero tendra algo así:

```bash
|DOMAIN|.="v=spf1 a mx ip4:|SERVER_IP||EXTRA_SPF||SPF_IPV6| -all"
_dmarc="v=DMARC1; p=none; sp=none;"
```

> NO es el alcance de este tutorial el explicar si usar o no usar **rua** en el registro, y lo que dice Google y lo que dice Microsoft.

![Edicion de la zona en el Tablero de Directadmin](https://multimedia.castris.com/imagenes/wiki/directadmin/Edit-DNS-Zone-hostname.jpg)

### Proceso adicional si el sistema de DNS no es el del servidor

Si como suele ser habitual, el control de DNS del hostname no es una DNS local o del cluster deberemos abrir el fichero de zona, ya sea en el Tablero de Directadmin o en el shell, para copiar el contenido de estos registro de zona y copiar en la zona del dns que controla el dominio del hostname. 

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# PHP en shell para usar wp cli en DirectAdmin

##  Introducción
En este caso y para facilitar las cosas a los usuarios de Directadmin, en el que como norma general se instalan distintas versiones de PHP y la principal del servidor puede ser incompatible connuestras necesidades es mejor usar la que necesitamos.

> Aunque este tip es para DirectAdmin, vale para cualquier distribución Linux o *nix. adaptando el tip a tu SO

[PHP en shell para usar wp cli - Version cPanel](https://wiki.castris.com/books/wordpress/page/problemas-por-la-version-shell-usada-y-wp-cli-cpanel-version)

### Ejemplo 
```bash
 wp core update && wp plugin upgrade --all && wp theme upgrade --all
Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead in /home/user/public_html/wiki.dominio.com/wp-includes/compat.php on line 502

 php -v
PHP 7.2.34 (cli) (built: Mar 28 2023 21:20:00) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with the ionCube PHP Loader + ionCube24 v10.4.5, Copyright (c) 2002-2020, by ionCube Ltd.
    with Zend OPcache v7.2.34, Copyright (c) 1999-2018, by Zend Technologies

```

El usuario tiene una versión correcta en la que todavía no estaba declarada obsoleta la función `__autoload()`

Si estamos como usuario 

```
$ which php
/usr/local/php74/bin/php
$ which wp
/usr/local/bin/wp
```


Ahora solo nos queda llamar al wp-cli de forma adecuada `74` es la versión que queremos usar
`/usr/local/bin/wp` es el path de instalación global de la herramienta wp 

```
/usr/local/php74/bin/php /usr/local/bin/wp core update
```

### Tip

Esto podemos añadirlo a nuestro fichero de configracion del shell usado.

Por ejemplo y para el caso de que tengamos mas de un sitio web con distintos requerimientos mejor usar este formato

```
wp74="/usr/local/php74/bin/php /usr/local/bin/wp"
wp82="/usr/local/php82/bin/php /usr/local/bin/wp"
```


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/cart.php?gid=18).

# Limpieza de mysql tras una migración: permisos de host antiguos

## Introducción

Cuando se realizan migraciones entre servidores, ya sea cPanel a Cpanel, cPAnel a Directamdin,, siempre quedan proquerías que hay que limpiar.

Una de ellas son las entradas en la tabla `user` de mysql/maridab en la que se conservan los hosts remotos de antiguos servidores. 

He visto alguna vez cuando me han contratado para un mantenimiento de un servidor y un tunning, servidores con mas de 1000 usarios mysql, y que llevaban arrastrando 4 o 5 hosts de distintos servidores, de migración en migración.

Luego, claro, mysql va lento. 

> Nota. No es precismanet la raíz del porblema, peoro cuando en el jardin dejas malas hierbas, al final caban poniendose feo.

### Revisión

Accedemos a nuestro mysql

```bash
> mysql
SELECT User, Host FROM mysql.user;
+--------------------------+--------------------+
| User                     | Host               |
+--------------------------+--------------------+
| cdbtnet                  | localhost          |
| cdbtnet                  | old.server.com     |
| cdbtnet                  | old2.server.com    |
...
```

### Cramos el script

El escript se ejecuta pasandole un parámetro:
-  El nombre del host a eliminar

******************************
**ATENCIÓN**

ESTE COMO TODOS LOS COMANDOS DE ELIMINACION DEBE HACER LEYYENDOSE, ENTENDIENDOLO Y CON ... BACKUP
**********************

```bash
#!/bin/bash

# Verificar que se haya pasado un parámetro
if [ $# -eq 0 ]; then
    echo "Debe proporcionar un host para eliminar los usuarios. Uso: $0 <host>"
    exit 1
fi

# Guardar el parámetro proporcionado como el host a eliminar
delete_host=$1

# Variables de conexión a la base de datos
db_user="da_admin"
db_password=$(grep 'passwd' /usr/local/directadmin/conf/mysql.conf | awk -F= '{print $2}')
db_host="localhost"  # Cambia si tu base de datos no está en localhost

# Comando para listar usuarios del host específico
list_users_command="SELECT user, host FROM mysql.user WHERE host='${delete_host}';"

# Conéctate a MySQL y obtiene la lista de usuarios
users=$(mysql -u "$db_user" -p"$db_password" -h "$db_host" -Bse "$list_users_command")

# Verificar si se obtuvieron usuarios
if [ -z "$users" ]; then
    echo "No se encontraron usuarios con host '$delete_host'."
    exit 0
fi

# Generar y ejecutar los comandos de eliminación de usuario
while IFS=$'\t' read -r user host; do
    if [ ! -z "$user" ] && [ ! -z "$host" ]; then
        drop_user_command="DROP USER '$user'@'$host';"
        mysql_command="mysql -u \"$db_user\" -p\"$db_password\" -h \"$db_host\" -e \"$drop_user_command\""
        echo "Ejecutando: $drop_user_command"

        eval $mysql_command

        # Verificar si el comando se ejecutó correctamente
        if [ $? -ne 0 ]; then
            echo "Error al ejecutar: $drop_user_command"
        else
            echo "Usuario eliminado con éxito: $user@$host"
        fi

    fi
done <<< "$users"
```

```
chmod +x nombre_script.sh
./nombre_script.sh 5.0.0.0
....
Ejecutando: DROP USER 'tblrmmll_root'@'5.0.0.0';
Comando que se ejecutará: mysql -u "da_admin" -p"ElPaSsWoRd" -h "localhost" -e "DROP USER 'tblrmmll_root'@'5.0.0.0';"
...
```


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Activar DKIM a todos los dominios de un servidor con Directadmin

## Introducción
DKIM es pieza fundamental en el sistema actual de antispam, y sobre todo para garantizar que nuestro dominio y/o Ip no loleguen a ser considerados como SPAM.

### Configuracion DKIM global

Indicada en el manual, [directadmin.conf - #dkim](https://docs.directadmin.com/directadmin/general-usage/all-directadmin-conf-values.html#dkim)  nos indica que la variable es `dkim` 

| Valor | Comentario                                                                                 |
|-------|-------------------------------------------------------------------------------------------|
| 0     | DKIM está deshabilitado por defecto para los nuevos dominios                               |
| 1     | DKIM está habilitado por defecto para los nuevos dominios                                  |
| 2     | La funcionalidad de DKIM está habilitada, pero no es obligatoria para los nuevos dominios |

Podemos editarlo manualmente o usar por ejemplo: 

```bash
da config-set dkim 1
cd /usr/local/directadmin/custombuild
./build update
./build exim
./build eximconf
```

o

```
da build update
da build exim
da build eximconf
```

Esta configuración hará que todos los dominios futuros, se configuren con **DKIM.**

### Para dominios existentes

Es probable que en una migración o situación especifica, el administrador deseé poner el valor a `2` para poder realizar la migración sin añadir una capa de complejidad.

Bien, una vez asentado todo, o bien porque había dominios anteriores sin **DKIM,** procedemos: 

> Atención: Si hay ya dominios que no quieren tener **DKIM**, ya sea porque no saben como usar servicios exteriores de correo sin configurarlo correctamente o porque no se lo explicarón, se puede producir problemas con ellos. Tener en cuenta.

Habilitar la creación automática de registros **DKIM** no afecta a los dominios existentes. Tienes algunas opciones para agregar **DKIM** a los dominios antiguos una vez que has habilitado **DKIM** en el archivo `directadmin.conf`. Puedes hacerlo para cada dominio uno por uno o para todos los dominios existentes a la vez.

Para habilitar **DKIM** para todos los dominios existentes después de configurar **DKIM** en `1` en el archivo directadmin.conf, puedes ejecutar el siguiente comando a través de **SSH** como el usuario root. 

```bash
echo "action=rewrite&value=dkim" >> /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq
```

Para habilitar **DKIM** solo para dominios seleccionados uno por uno, utiliza ya sea la cola de tareas o el script `dkim_create.sh` proporcionado por DirectAdmin (reemplaza **DOMAIN.COM** con el dominio para el cual deseas habilitar **DKIM**).

```bash
/usr/local/directadmin/scripts/dkim_create.sh DOMAIN.TLD
```

o inmeditamente

```bash
echo "action=rewrite&value=dkim&domain=DOMAIN.COM&dns=yes" >> /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq
```

> Ambos comandos funcionan de la misma manera, con la excepción de que puedes tener el **DKIM** escrito inmediatamente con **dataskq** en comparación con dentro de un minuto utilizando el script `dkim_create.sh`.

## Deshabilitar DKIM por Usuario Cuando Está Habilitado o Permitido Globalmente

Esta función está diseñada para que **DKIM** esté habilitado o permitido globalmente, pero puedes deshabilitarlo a nivel de usuario. 

Esto requiere que se establezca `dkim=1` o `dkim=2` en el archivo `directadmin.conf`. 

La configuración de `1` habilitará **DKIM automáticamente** para todos los dominios bajo cada usuario, a menos que especifiques lo contrario en sus archivos `user.conf`. 

La configuración de `2` les permitirá a los usuarios, habilitar **DKIM** por sí mismos, a menos que se especifique lo contrario en su archivo `user.conf`.

Ten en cuenta, que establecer `dkim=0` en el archivo `directadmin.conf` **deshabilita completamente** **DKIM** para todo el servidor y los archivos `user.conf` no se verificarán. Por lo tanto, si deseas que **DKIM** esté deshabilitado globalmente por defecto con la opción de habilitarlo solo para ciertos usuarios/dominios, configurar `dkim=2` en el archivo directadmin.conf es una mejor opción.

Cuando creas un usuario, se crea con un dominio predeterminado. Este dominio tendrá **DKIM** creado para el dominio por defecto debido a la configuración habilitada globalmente y la no existencia de un `user.conf` hasta que se cree la cuenta (no hay un `user.conf` para editar y deshabilitar **DKIM**). 

Por lo tanto, se deberá eliminar manualmente **DKIM** para el dominio predeterminado si prefieres que no tenga un registro **DKIM**. 

Aquí están los pasos para, primero eliminar el registro del dominio predeterminado y luego deshabilitar la creación automática de registros DKIM para los dominios subsiguientes bajo ese usuario.

Para eliminar los registros del dominio predeterminado, elimina el registro **TXT** `x._domainkey` de `/var/named/DOMAIN.TLD.db` y luego elimina las claves.

```bash
rm -f /etc/virtual/DOMAIN.COM/dkim.public.key
rm -f /etc/virtual/DOMAIN.COM/dkim.private.key
```

Ahora, edita el `user.conf` para que no se habiliten registros **DKIM* para los dominios que se creen posteriormente bajo el usuario. El `user.conf` se puede editar a través de **SSH** como el usuario root y el archivo se encuentra aquí (donde USERNAME representa el nombre de usuario del usuario que estás editando), 

```bash
/usr/local/directadmin/data/users/USERNAME/user.conf
```

Esto permite que la configuración de **DKIM** de ese usuario particular anule la configuración de **DKIM** habilitada globalmente establecida en el archivo `directadmin.conf`, evitando así que se creen registros **DKIM** para los dominios de ese usuario en adelante.

Reinicia DirectAdmin después de hacer los cambios.

```bash
systemctl restart directadmin
```

## Deshabilitar DKIM a Nivel de Dominio

Esta es esencialmente la misma función que la característica a nivel de usuario, con la excepción de que editarías el archivo de configuración del dominio ubicado en `/usr/local/directadmin/data/users/USERNAME/domains/DOMAIN.TLD.conf`, en su lugar. 

No es necesario editar el archivo `user.conf` del usuario para controlar **DKIM** a nivel de dominio.

Si un determinado dominio utiliza tanto un DNS remoto como un servidor de correo remoto, es posible que desees deshabilitar DKIM para este dominio en particular en lugar de para todos los dominios del usuario. Aquí es donde esta función es útil.

> En realidad no es necesaria esta dudosa práctica usada, cuando el usuario final o la otra empresa que le ofrece servicios de correo, no le da las putas adecuadas) La gran mayoría de los sevricios de correo externo, si dan la información necesaria para crear y mantener un registro **DKIM** para cada servicios de correo, basandonos en el registro **selector** pero su complejidad y la falta de conocimientos llevan a un salida incorrecta. Pero este es otro tema del que hablaremos en otro post, en fechas próximas.

Reinicia DirectAdmin después de hacer cambios en el archivo DOMAIN.TLD.conf:

```bash
systemctl restart directadmin
```

## Cambiar el Selector Predeterminado

DirectAdmin utiliza `x` como el selector predeterminado. Para cambiar el selector, necesitarás actualizar el archivo directadmin.conf con el selector deseado. Los siguientes ejemplos cambian el selector a `default`:

```bash
/usr/local/directadmin/directadmin set dkim_selector default restart
```

Necesitarás reconstruir la configuración de Exim de la siguiente manera:

```bash
/usr/local/directadmin/custombuild/build exim_conf
```

Confirma que el selector fue cambiado en el archivo `/etc/exim.dkim.conf`:

```
❯ grep -i selector /etc/exim.dkim.conf
  dkim_selector = x
```

Ahora, cualquier registro DKIM creado nuevo utilizará tu selector especificado. Ten en cuenta que necesitarás eliminar y recrear los registros DKIM antiguos que utilicen el selector antiguo si deseas que usen el nuevo selector.

## Conclusión

Ya hemos revisado el procedimiento paso a paso para habilitar DKIM con el panel de control DirectAdmin. 
Podemos decir, qu el registro **DNS DKIM** es esencial para un servicio de correo electrónico más fluido y para la autenticación de correos electrónicos.
##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Mod Security. Desactivación global de reglas por path

## Introducción

Una vez más, con el tema de Mod Security, compruebo que el 99% de los administradores de sistemas, sigue la misma pauta de siempre, la salida hacia adelante más fácil, deshabilitando todo.

Soy cabezón, y no he basado mi seguridad en mis máquinas, en abrir la puerta menoscabando la seguridad perimetral. 

Tengo cliente a los que llevo sus servicios, que son incapaces de aguantar en firme, con las normas y reglas de seguridad que trato de imponerles, pese a que eso suponga un aumento de costes en horas para limpiar problemas derivados de los **mini hackeaos**

## Escenario

En el caso que me llevó a este tema, estaba implicado [DirectAdmin](https://directadmin.com) y el webmail [RoundCube](https://roundcube.net) en una situación algo especial.

Con las cuentas de sistema `usuario` usadas como cuenta de correo, RoundCube recibía una serie de errores al lanzarse prohibiciones vía Mod Security.

> Connection Error (Failed to reach the server)!
> !Error de conexión fallo al intentar alcanzar el servidor)!

> Cuando teneos problemas con RoundCube como con muchas aplicaciones web, insisto a mis clientes que observen las webmaster tools del navegador y/o al menos las cabeceras de respuesta (406) más allá de los mensajes tipo **Alert** de las aplicaciones.

![Webmaster Tools - Navegador](https://multimedia.castris.com/imagenes/wiki/directadmin/ROUNDCUBE-406-NOT-ACCEPTABLE-02.jpg)

Al final, localizando las reglas afectadas por RoundCube a nivel hostname, salieron a la palestra las siguiente reglas afectadas:

- 911100
- 932260
- 920340
- 932235
- 941100
- 941130
- 941160
- 941170
- 949110
- 980130

Todas ellas, con Paranoia Level 1, [ModSecurity :: Concepto Paranoia Level](https://coreruleset.org/docs/concepts/paranoia_levels/)

Uy... que cosas. EL **PR 1** es básicamente el nivel con menor número de falsos positivos, y muchas de estas rules, ofrecen una protección extraordinaria a muchos de los errores más comunes de los "programadores" de javascript.

Así que las soluciones que se presentan en, StackOverFlow, el foro de DirectAdmin, y otras muchas, no me convencían, porque se trata de atajos (workaround) que llevan a la desactivación de la regla de forma global, o en su defecto, no cumplen con la documentación de Mod Security o de DirectAdmin.

## Solución (Actualizado 8/01/2025)

Al final vi el método antiguo (me llama mas la atención que el nuevo) ofrecido por Directadmin y que usamos en [Como bloquear los Bad bots (Bot basura) usando ModeSecurity en Directadmin nueva](https://wiki.castris.com/books/directadmin/page/como-bloquear-los-bad-bots-bot-basura-usando-modesecurity-en-directadmin-v1) que es mas manejable y programable. 


### /usr/local/directadmin/custombuild/custom/modsecurity/conf/

Hay que crear este path como repositorio de los ficheros especificos que creemos para el manejo de ModSecuirty sin miedo a que se eliminen.

```bash
mkdir -p /usr/local/directadmin/custombuild/custom/modsecurity/conf/
```

### Fichero de exclusiones

En mi caso uso `/usr/local/directadmin/custombuild/custom/modsecurity/conf/01_REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf`, pero como es una precarga, le asigno nuemros bajos como 00, 01, 02 con el fin de que sean los primeros en cargar. 

Tenemos un ejemplo de como tratar el tema en el fichero `/etc/modsecurity.d/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example` 

```bash
 cp  /etc/modsecurity.d/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example /usr/local/directadmin/custombuild/custom/modsecurity/conf/01_REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
```

Una vez creado, o modificado, **SIEMPRE** debemos verificar primero (si usamos nginx o apache+nginx) que no hay ningun problema, con. Para ello escribimos la rule en el fichero real, `/etc/modsecurity.d/01_REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf` (copiar y pegar)  

```
nginx -t
```

Una vez verificado, procedemos a reconstruir los ficheros de configuracion con:

```
> da build modsecurity_rules
Installing OWASP Core Rule Set for ModSecurity...
download_cached: using cached '/usr/local/directadmin/custombuild/cache/owasp-modsecurity-crs-4.9.0.tar.gz' file
Copying custom ModSecurity rules to /etc/modsecurity.d/...
Installation of ModSecurity Rule Set has been finished.
```

Después reconstruimos la configruación.

```
> da build rewrite_confs
cp: cannot remove '/etc/httpd/conf/extra/httpd-directoryindex.conf': Operation not permitted
2025/01/08 17:15:27  info executing task            task=action=rewrite&value=ips
2025/01/08 17:15:27  info finished task             duration=10.262712ms task=action=rewrite&value=ips
Using 5.135.93.75 for your server IP
Copying custom ModSecurity rules to /etc/modsecurity.d/...
Restarting apache.
Installing OWASP Core Rule Set for ModSecurity...
download_cached: using cached '/usr/local/directadmin/custombuild/cache/owasp-modsecurity-crs-4.9.0.tar.gz' file
Copying custom ModSecurity rules to /etc/modsecurity.d/...
Installation of ModSecurity Rule Set has been finished.
2025/01/08 17:15:31  info executing task            task=action=rewrite&value=ips
2025/01/08 17:15:31  info finished task             duration=7.557145ms task=action=rewrite&value=ips
Using 5.135.93.75 for your server IP
Using 5.135.93.75 for your server IP
Copying custom ModSecurity rules to /etc/modsecurity.d/...
2025/01/08 17:15:34  info executing task            task=action=rewrite&value=nginx
2025/01/08 17:15:40  info finished task             duration=6.360399294s task=action=rewrite&value=nginx
Restarting nginx.
```

Ya podriamos hacer pruebas.


### Ejemplos

```apache
SecRule REQUEST_FILENAME "@beginsWith /roundcube" \
     "id:1001,\
     phase:2,\
     pass,\
     nolog,\
     ctl:ruleRemoveById=911100,\
     ctl:ruleRemoveById=932260,\
     ctl:ruleRemoveById=920340,\
     ctl:ruleRemoveById=932235,\
     ctl:ruleRemoveById=941100,\
     ctl:ruleRemoveById=941130,\
     ctl:ruleRemoveById=941160,\
     ctl:ruleRemoveById=941170,\
     ctl:ruleRemoveById=949110,\
     ctl:ruleRemoveById=980130"
``` 

 

## Idea y apuntes
- [ModSecurity Rules: Global or for Hostname?](https://forum.directadmin.com/threads/modsecurity-rules-global-or-for-hostname.71853/)
- [How do I skip certains rules for parameter in a path in ModSecurity?](https://stackoverflow.com/questions/38232609/how-do-i-skip-certains-rules-for-parameter-in-a-path-in-modsecurity)


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Reconstrucción del indice Full Text Search (FTS) en cuentas de correo :: Dovecot

## Introducción

A veces ocurre que la correspondencia entre el tamaño en disco de una cuenta de correo usando los medios reales como son `du` no se corresponde con lo que DirectAdmin nos muestra en el panel de usuario para dicha cuenta de correo.

Esto suele ocurrir tras una eliminación de correo muy intensa en la que por cuestiones de espacio el usuario quiere ver liberada su couta por estar cerca de excederse o haberse excedido ya. Tmbien puede ocurri que sea un problema que requiere de un reconstrucción forzada.

Esos indices de busqueda pueden ser gigantes, del orden de varios GB. 

## doveadm-fts(1) - 

[Manipulate the Full Text Search (FTS) index](https://doc.dovecot.org/main/core/man/doveadm-fts.1.html)

Basicamente podemos hacer lo siguiente:

```
## Cuenta E-mail
❯ doveadm fts rescan -u jmvarela@omnicon.es
## Usuario y todas sus cuentas
❯ doveadm fts rescan -u USERNAME
```

Tambien podemos usar un script para usarlo como mantenimiento de estos indices.

###  rescan_fts.sh

Cremos el fichero 

```bash
#!/bin/bash

# Directorio que contiene los usuarios
USER_DIR="/usr/local/directadmin/data/users"

# Iterar sobre cada usuario (directorio)
for user in "$USER_DIR"/*; do
    # Verificar si el item es un directorio
    if [ -d "$user" ]; then
        # Obtener el nombre del usuario
        username=$(basename "$user")
        echo "Ejecutando doveadm fts rescan para el usuario: $username"
        # Ejecutar el comando doveadm fts rescan
        doveadm fts rescan -u "$username"
    fi
done
```
Damos permiso y ejecutamos

```
chmod +xrescan_fts.sh
./rescan_fts.sh
```

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Cambio el doc root de un dominio en DirectAdmin

## Doc root, public, public_html
El software moderno desde hace años, evita la publicación del software en el mismo area publica o expuesta a internet. Por seguridad, por ordenación, el codigo usa el contenido en distintas carpetas dejando a una carpeta el destino de exposición al publico.

Este puede ser `public` como en el caso de Laravel, u otros distintos segun el framework. 

Esto suele ser un problema para los hosters porque sus paneles de control usand el de toda la vida, `public_html`

En el camino, mucho se aventuran a enlace simbolicos, redirecciones. Y todo eso es muy bonito pero al final altera, no solo la realidad de la instalación sino un posible cambio de servidor, de paradigma de sistema, etc. 

### Cambiar el DOC ROOT en Directadmin

Si bien es sencillo, a veces parace que los manuales son algo espesos, y si encima tenemos un for de mas de 20 años de antiguedad, sin prunning, pues acabas por volverte loco con lso cambios en el tiempo.

En la actualidad es bien sencillo.

#### (Admin) Custom HTTPD Configurations

`evo/admin/custom-httpd`

Seleccionar el dominio (o subdominio entendiendose como entidad configurada como dominio independiente)

Después, deberemos modificar la configuracionde `httpd.conf` `nginx.conf.proxy`

![Custom Httpd Configurations](https://multimedia.castris.com/imagenes/wiki/directadmin/da-customize_web_root.jpg)

En cada uno de ellas veremos un boton con la palabra **Customize**

![Customize HHTPD](https://multimedia.castris.com/imagenes/wiki/directadmin/da_customize_button_httpd.jpg)

Que la hacer click nos mostrará la caja de exto donde introducir nuestros cambios.

![Customize configuration](https://multimedia.castris.com/imagenes/wiki/directadmin/da-customize_httpd_conf_vars.jpg)

Aqui usando varmiables podemos adecuar a lo que queramos nuestros path. Ejemplo de abajo me sirve a mi porque se trata de una migracion que no queria complicar.

```
|?DOCROOT=`HOME`/domains/`DOMAIN`/public_html/public|
```

El resultado de eso sera que tras reconstruir la configuracion del `doc root` será `/home/user/domains/subdomain.domain.tld/public_html/public` 

De esta forma el proyecto estará en el public_html, pero solo estará expuesta la carpeta `public`

#### da build rewrite_confs

Siemrpe, cuando terminemos deberamos reconstruir los ficheros de configruación, lo cual se hace con el comando `da build rewrite_confs` o bien en el botón de

![Custom HTTPD Configuration :: da build rewrite_confs](https://multimedia.castris.com/imagenes/wiki/directadmin/da_build_rewrite_confs.jpg)


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Donde está la configuracion básica del servidor con Directadmin

## Directadmin configuración interna

Directadmin admin es más sencill oque otros panales de controla efectosd e localizar donde estan las conifguraciones que despliega en el servidor.

Directadmin, compila y configura el software de servidor, como Web (Apache, Nginx,..) Correo (Imap, exim,...) desde dos puntos claves:

### Software de servicio
```
 /usr/local/directadmin/custombuild/options.conf
```
### Directadmin (interno)

```
 /usr/local/directadmin/conf/directadmin.conf
```

Con estos fichero spodemos trabajar cosas como templates, clones, etc. 

> Esta entrada es una entrada rápida, de conocimiento básico.

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Cambios especificados en el php.ini disable_functions por dominio (Directadmin example)

## Cambiar individualmente en un dominio disable_functions

Un escenario muy común en sistemas y mas en estos cuando se usa **Directadmin**, es la salida hacia adelante obviando los problemas que puede causar nuestra decisión. 

Un caso común es el quitar las protecciones de forma global a través de el proceso general del servidor, en lugar de buscar una solución individual, para el caso concreto.

En el caso de Directadmin, si tenemos la opción [secure_php](https://docs.directadmin.com/webservices/php/secure-php.html#how-to-enable-secure-php) esta añadirá a la configuración del php del servidor  

```php
disable_functions = exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
mysqli.allow_local_infile = Off
expose_php = Off
register_globals = Off
```

Existe el camino fácil, que es ir quitando las funciones que molestan a nuestros clientes, [How to customize the disable_functions list](https://docs.directadmin.com/webservices/php/secure-php.html#how-to-customize-the-disable-functions-list) y existe otro camino, que es desahbilitar al que nuestro cliente necesita, si esto es totalmente necesario, ya sea por un tiempo limitado, o de forma definitiva.

### Opciones de php.ini por dominio

[Opciones por dominio](https://docs.directadmin.com/webservices/php/php-options.html#_1-per-domain-settings) 
o [How to use per-domain or per-path settings for php](https://docs.directadmin.com/webservices/php/php-options.html#how-to-use-per-domain-or-per-path-settings-for-php)
nos ayuda a esto último ya que tratar de modificar `disable_functions` via `.user_ini` aunque este autorizado, no funcionará para modificar esa clave. 

- Lo primero es buscar el path de nuestra instalación para nuestro Directadmin y nuestra distro.

En mi caso esta en `/usr/local/phpXX/lib/php.conf.d`

Así que, siguiendo el manual, introducido el path y el dominio o subdominio del que quiero tal corrección, el nos lo dará.

En nuestro caso el cliente tenia algunos despliegues con Laravel que necesitaban `shell_exec` por lo que lo eliminamos del fichero **ini** personalizado, 

```
/usr/local/php83/lib/php.conf.d/30-subdomain.full.tld.ini
```
```
disable_functions = exec,system,passthru,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
```

Después simplemente tenemos que hacer un rebuild de la función de seguridad.

```
da build secure_php
```

### jit opcache.jit_buffer_size

Esta opción como muchas otras de php, tiene esta cuestión activa:

```
< opcache.jit_buffer_size es PHP_INI_SYSTEM, lo que significa que **solo puede definirse en php.ini o php-fpm.conf, no en .user.ini ni .htaccess.
```

Esto fuerza a usar el método de cambio 

##### Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Git desde DirectAdmin Interface

## Git desde DirectAdmin Interface

Jamás le aconsejaría a nadie  usar el Git a través de un interface visual de este tipo, pero a veces por cuestiones de seguridad, metodologías y otras cuestiones, e las que el acceso a SSH esta restringido, puede ser el último recurso.

Aun así, no imagino una jefe de proyectos lidiando con un interface de esta naturaleza en la que por hacer fácil la cosa, el final se hace difícil.


En primer lugar es imperativo leer la documentación, (Git manager feature)[https://docs.directadmin.com/other-hosting-services/git/general.html]

Aquí tendremos una idea general de lo que nos espera.

- El proyecto como root estará en el path que indiquemos 
- La configuración no esta en el root de nuestro proyecto, sino en `/home/USER/domains/DOMAIN/name.git` lo cual tendremos que recordarlo, si como es bine seguro, algún día deberemos lidiar con Git para arreglar o realizar operaciones más complejas que un simple **click** para hacer el equivalente a un **git pull**

Es importante la parte final, (Technical)[https://docs.directadmin.com/other-hosting-services/git/general.html#technical] donde veréis la información sobre como ejecutar `git` usando comando y las opciones a escribir parta entenderlo mejor.


To desde luego, no le aconsejo a ningún desarrollador o jefe de proyecto, el uso de esta interface. Menos si no tiene acceso a `root` ya que la BD con los datos del git estará ubicada en `/usr/local/direcadmin/data/users/USER/user.db` y las complejidades de uso no merecen la pena, a cambio de negarse a usar el terminal.


### Nota
En estos tiempos, es muy sistémico, la falta de conocimientos de sistemas. Hemos llegado a un punto de especialización y de venta de supuestos sistemas amigables, que en realidad no lo son. Desconocer el uso de SSH, de los comandos básicos de git, de el como de aquellas cosas que operan en nuestro espacio profesional, es un serio handicap para la industria. Parece que aquí llego también el paradigma del **conocimiento mínimo** o lo que es lo mismo la **falta de formación profesional adecuada**


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Como bloquear los Bad bots (Bot basura) usando ModeSecurity en Directadmin v1

## Introducción BadBots

El término "bot" se utiliza frecuentemente en internet y se refiere a un programa informático que automatiza acciones o tareas en la red. Aunque un bot no es inherentemente bueno o malo, puede clasificarse en alguna de estas dos categorías, dependiendo de si se utiliza con buenas o malas intenciones.

### Bots Buenos

Se llama "bot bueno" a aquel que realiza tareas útiles o beneficiosas que no perjudican la experiencia del usuario en internet. Hay muchos bots que se consideran buenos, por ejemplo:

- Bots de Motores de Búsqueda: A menudo conocidos como rastreadores web o arañas, son operados por grandes motores de búsqueda como Google o Bing.
- Bots de Monitoreo de Sitios: Estos bots supervisan métricas de páginas web, como el seguimiento de enlaces o caídas del sistema, y pueden alertar a los usuarios sobre cambios importantes o tiempos de inactividad. Son utilizados por sitios como UptimeRobot o Cloudflare.
- Bots de Feed: Estos bots recorren internet en busca de contenido para añadir a los feeds de noticias de diversas plataformas, y son gestionados por sitios de agregación o redes sociales.
- Bots de Asistentes Personales: Aunque estos programas son más avanzados que un bot típico, siguen siendo considerados bots. Son programas informáticos que buscan datos en internet que coincidan con una búsqueda, y son operados por empresas como Apple (Siri) o Google (Alexa).

### Bots Malos

Por otro lado, se refiere como "bot malo" a aquellos que realizan actos maliciosos, roban datos o causan daños en servidores, redes o sitios web. Pueden ser empleados para llevar a cabo ataques de denegación de servicio distribuido (DDoS) o para escanear servidores, redes o páginas web en busca de vulnerabilidades que puedan comprometer estos sistemas.

En los últimos años, hemos visto que los bots maliciosos se han convertido en un problema significativo tanto para los administradores de servidores como para los dueños de sitios web. Estos bots suelen dirigirse a un servidor o página web, realizando miles de solicitudes y recopilando grandes cantidades de datos en un tiempo muy corto.

Su practica, su diseño, y su falta de ética son un problema para muchos sitos, sus administradores y los administradores de sistemas.


### Técnicas de bloqueo

Hay algunas técnicas de bloqueo como el uso de .htaccess a través de formulas como la expuesta en [Bad Bots y la pesadilla del tráfico. Htaccess en Apache 2.4](https://wiki.castris.com/books/tips-sobre-la-administracion-de-linux/page/bad-bots-y-la-pesadilla-del-trafico-htaccess-en-apache-24):

```bash
# Start Bad Bot Prevention
<IfModule mod_setenvif.c>
# SetEnvIfNoCase User-Agent ^$ bad_bot
SetEnvIfNoCase User-Agent "^12soso.*" bad_bot
SetEnvIfNoCase User-Agent "^192.comAgent.*" bad_bot
SetEnvIfNoCase User-Agent "^1Noonbot.*" bad_bot
...
<Limit GET POST PUT>
  Order Allow,Deny
  Allow from all
  Deny from env=bad_bot
</Limit>
</IfModule>

```


Pero esto es una pesadilla a nivel administrador de sistemas, donde cada uno pone su lista.

Para mi, la mejor es el uso de ModSecurity y como ya me dedico prioritáriamente a Directadmin lo dejaré aquí más claro.

### Bad Bots bloqueados en Directadmin con ModSecurity
Para usar este método tenemos que hacerlo de manera que no se sobre escriba la configuración cuando se actualiza Directadmin, Apache o Nginx

#### Crear el directorio si no existe
```
cd /usr/local/directadmin/custombuild
mkdir -p custom/modsecurity/conf 
```

#### Crear 00_bad_bots_conf 

```
nano /usr/local/directadmin/custombuild/custom/modsecurity/conf/00_bad_bots.conf
```

Contenido

```
# BLOCK BAD BOTS
SecRule REQUEST_HEADERS:User-Agent "@pmFromFile bad_bot_list.txt" "phase:2,t:none,t:lowercase,log,deny,severity:2,status:406,id:1100000,msg:'Custom WAF Rules: WEB CRAWLER/BAD BOT'"
```
> Atención a la rule ID, para que no choque con otra rules si tenias con anterioridad alguna adicional en otro sistema, o tienes un sistema para controlar las rules tuyas. Aquí usaremos `1100000`

#### Crear bad_bot_list.txt
Esta lista puedes actualizarla con la lista [Apache Ultimate Bad Bot](https://github.com/mitchellkrogza/apache-ultimate-bad-bot-blocker)

El fichero a usar es `https://raw.githubusercontent.com/mitchellkrogza/apache-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents-htaccess.list` 

```
wget -O /usr/local/directadmin/custombuild/custom/modsecurity/conf/bad_bot_list.txt  https://raw.githubusercontent.com/mitchellkrogza/apache-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents-htaccess.list
```

O con curl
```
curl -o /usr/local/directadmin/custombuild/custom/modsecurity/conf/bad_bot_list.txt https://raw.githubusercontent.com/mitchellkrogza/apache-ultimate-bad-bot-blocker/master/_generator_lists/bad-user-agents-htaccess.list
```

También puedes crear una estrategia, para usando dicha lista eliminar o añadir los tuyos propios, cuando se actualice.

#### Actualización

```
da build modsecurity_rules
da build rewrite_confs 
```

#### Verificación
Puedes verificar que esta correcto con el siguiente comando, que te mostrará que lo usado se copio en el lugar apropiado.

```
ls -la /etc/modsecurity.d/*bad*
-rw-r--r-- 1 root root  199 Jan  4 09:24 /etc/modsecurity.d/00_bad_bots.conf
-rw-r--r-- 1 root root 5534 Jan  4 09:26 /etc/modsecurity.d/bad_bot_list.txt
```

#### Testing

```
curl -A "AiHitBot" https://example.com
  <html>
  <head><title>406 Not Acceptable</title></head>
  <body>
  <center><h1>406 Not Acceptable</h1></center>
  <hr><center>nginx</center>
  </body>
  </html>

curl -A "aihitbot" https://example.com
  <html>
  <head><title>406 Not Acceptable</title></head>
  <body>
  <center><h1>406 Not Acceptable</h1></center>
  <hr><center>nginx</center>
  </body>
  </html>

```

> Reemplaza example.com con un dominio del servidor 😎


Deberas obtener un **406 Not Acceptable** como respuesta

##### Agradecimientos

- [How to Block Bad Bots using ModSecurity with DirectAdmin](https://www.vpsbasics.com/security/how-to-block-bad-bots-using-modsecurity-with-directadmin/)




##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Wordpress Manager de Directadmin y wp cli problemas de memoria

## PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted 

Un cliente reporto que estaba usando la herramienta **Wordpress Manager** de **Directadmin** y cuando instaló le salió un error de memoria


```bash
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/miusuairoes/domains/midominio.es/public_html/wp-includes/class-wpdb.php on line 2320 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/miusuairoes/domains/midominio.es/public_html/wp-includes/class-wpdb.php on line 2320 PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/miusuairoes/domains/midominio.es/public_html/wp-includes/class-wpdb.php on line 2320 Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/miusuairoes/domains/midominio.es/public_html/wp-
includes/class-wpdb.php on line 2320
```
![Error de memoria Wordpress Manager Directadmin](https://multimedia.castris.com/imagenes/wiki/da/wordpress-manager-memory.jpg)

Que raro. El cliente trabaja ya con la modificación de `memory_limit` segun el método de Directadmin, y era verificable con un `phpinfo();`

## Prueba en shell

Para ver el tema más cerca, me fui al shell.

```bash
❯ wp
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/unilanges/domains/MYDOMAIN.ES/public_html/wp-includes/class-wpdb.php on line 2320
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/unilanges/domains/MYDOMAIN.ES/public_html/wp-includes/class-wpdb.php on line 2320
PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/unilanges/domains/MYDOMAIN.ES/public_html/wp-includes/class-wpdb.php on line 2320
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 47526080 bytes) in /home/unilanges/domains/MYDOMAIN.ES/public_html/wp-includes/class-wpdb.php on line 2320
```

Y claro no quedo otra:

```bash
❯ php  -i | grep memory
memory_limit => 128M => 128M
Collecting memory statistics => No
opcache.memory_consumption => 128 => 128
opcache.preferred_memory_model => no value => no value
opcache.protect_memory => Off => Off
```

Como es **Directadmin**, su compilación trata el tema de una manera particular, y no encontré el como modificar el cli, para el cliente de php.

Así que conociendo el tema, la cuestión era probar lo más sencillo del mundo. Modificar el `wp-config.php`

```php
define( 'WP_MEMORY_LIMIT', '256M');
```

Y voila. Ya no sale el error, y ya podemos usar el `wp-cli` en el shell. Ambas dos cosas solucionadas. 

### Notas
Directadmin tiene muchas herramientas de ayuda, pero sigo pensando que el panel debería ser más orientado a sistemas, que ayudas al usuario.

Un gestor de Wordpress me parece un mal camino, como el del [git](https://wiki.castris.com/books/directadmin/page/git-desde-directadmin-interface). 

Pero esto es una opinión.

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Configurar PHP en un servidor con Directadmin

Antes de acudir a medidas como el uso de `php.ini` o `.user.ini` debemos saber que **Directadmin** en modo usuario pone a disposición una entrada para poder configurar nuestro PHP, en aquellos servidor que asi lo tiene posibilitado y usan PHP-FPM como es el caso de [Castris](https://castris.com)

## Pasos 

El proceso lógico, que además sirve para otras habilidades del panel, es el que se describe mas abajo.


1. Usar el buscador si no conocemos la ubicación del sitio para la acción a realizar

![Panel principal - Buscador de menu](https://multimedia.castris.com/imagenes/wiki/da/directadmin_panel_principal.jpg)

3. Realizar una busqueda corta, de tipo generalista

![Buscador - búsqueda parcial](https://multimedia.castris.com/imagenes/wiki/da/directadmin_buscador_configuracion_panel.jpg)

4. Realizar los cambios

![Configuración PHP](https://multimedia.castris.com/imagenes/wiki/da/directadmin_php_configuracion.jpg)

- Selección del sitio web
- Cambios a de versión PHP
- Cambios posibles a realizar de uno en uno y cada un con sus valores predefinidos
  - display_errors
  - error_reporting
  - file_uploads
  - include_path
  - log_errors
  - mail.force_extra_parameters
  - max_execution_time
  - max_file_uploads
  - max_input_time
  - max_input_vars
  - memory_limit
  - post_max_size
  - register_globals
  - session.gc_maxlifetime
  - short_open_tag
  - upload_max_filesize
  - zlib.output_compression


### Importante: en una cuenta con varios dominios no todo tiene el mismo alcance

Aunque esta pantalla se abre con un sitio web seleccionado, **no todos los cambios se aplican solo a ese sitio**:

- **La versión de PHP** se aplica **únicamente al dominio seleccionado**. Hay que repetir el cambio en cada dominio.
- **El resto de valores** (`memory_limit`, `upload_max_filesize`, `max_execution_time`, etc.) se aplican a **todos los dominios de la cuenta**, porque Directadmin usa un único pool de PHP-FPM por usuario.

Detallado en [Versiones de PHP y php.ini en cuentas con varios dominios](https://wiki.castris.com/books/directadmin/page/versiones-de-php-y-phpini-en-cuentas-con-varios-dominios).

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Configuración de Smart Relay en DirectAdmin/Exim

## Introducción al smart relay para Directadmin con Exim

DirectAdmin proporciona un mecanismo de inclusión de archivos para personalizar la configuración de Exim sin perder los cambios en actualizaciones posteriores. Para la configuración de smart relay, se utilizan dos archivos de inclusión específicos que se cargan en diferentes secciones del archivo principal `exim.conf`.

## Ubicación de las Inclusiones

En el archivo principal de configuración de Exim (`/etc/exim.conf`), encontramos dos inclusiones importantes:

1. Para los transportes:
```
begin transports
.include_if_exists /etc/exim.transports.pre.conf
```

2. Para los routers:
```
begin routers
.include_if_exists /etc/exim.routers.pre.conf
```

## Archivos de Configuración

### 1. Transport Configuration (`/etc/exim_directadmin/exim.transports.pre.conf`)

Este archivo define cómo se realizará el envío de correo al smart host:

```plaintext
spamgateway_smarthost_transport:
 driver = smtp
 hosts_require_tls = *
.include_if_exists /etc/exim.dkim.conf
```

Desglose de la configuración:
- `driver = smtp`: Utiliza el protocolo SMTP estándar para el envío
- `hosts_require_tls = *`: Fuerza el uso de TLS para todas las conexiones
- Incluye la configuración DKIM para asegurar que los correos salientes sean firmados correctamente

### 2. Router Configuration (`/etc/exim_directadmin/exim.routers.pre.conf`)

Este archivo define las reglas de enrutamiento para el correo saliente:

```plaintext
spamgateway_smarthost_router:
 driver = manualroute
 domains = ! +local_domains
 ignore_target_hosts = 127.0.0.0/8
 condition = "${perl{check_limits}}"
 transport = spamgateway_smarthost_transport
 route_list = * smart02.domain.tld:hetzner-smart01.domain.tld
 hosts_randomize = true
```

Desglose de la configuración:
- `driver = manualroute`: Define un enrutamiento manual para los correos
- `domains = ! +local_domains`: Aplica solo a dominios que no son locales
- `ignore_target_hosts = 127.0.0.0/8`: Evita el envío a direcciones locales
- `condition = "${perl{check_limits}}"`: Verifica límites de envío mediante una función Perl
- `transport = spamgateway_smarthost_transport`: Utiliza el transporte definido anteriormente
- `route_list`: Define los smart hosts disponibles
- `hosts_randomize = true`: Habilita el balanceo de carga aleatorio entre los smart hosts

## Funcionamiento

Con esta configuración:
1. Todo el correo saliente (excepto para dominios locales) se enviará a través de los smart hosts configurados
2. El sistema alternará aleatoriamente entre los smart hosts disponibles (`smart02.domain.tld` y `hetzner-smart01.domain.tld`)
3. Si un smart host falla, el sistema intentará automáticamente con el otro
4. Las conexiones se realizan con TLS
5. Los correos mantienen la firma DKIM gracias a la inclusión de la configuración DKIM en el transporte

## Notas Importantes

1. La configuración mantiene la firma DKIM en los correos reenviados
2. El balanceo de carga entre smart hosts proporciona redundancia y distribución de carga
3. La configuración se mantiene incluso después de actualizaciones de DirectAdmin o Exim
4. Los cambios se realizan en archivos separados, lo que facilita el mantenimiento y la depuración

## Verificación

Para verificar que la configuración está funcionando correctamente, puedes:
1. Revisar los logs de Exim para confirmar el uso alternado de los smart hosts
2. Verificar que los correos salientes mantienen la firma DKIM
3. Comprobar que el sistema cambia automáticamente al host alternativo si uno falla


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Redis: Rotura de WordPress por error de Redis en Directadmin

## Escenario

- Sitio WordPress configurado con Redis Object Cache.
- El sitio deja de funcionar mostrando el mensaje *Error Establishing a Redis Connection*

Al ejecutar `systemctl`, se observa que:

- `redis-server` (Redis principal) está activo.
- `redis-rspamd` y `redis@user_a` están fallando.

## Análisis

Buscamos informacion sobre el estadod el servicio

```bash
systemctl | grep redis
● redis-rspamd.service      loaded failed failed    Multi-user redis persistent key-value database
  redis-server.service      loaded active running   Advanced key-value store
  redis@user.service        loaded active running   Multi-user redis persistent key-value database
  system-redis.slice        loaded active active    Slice /system/redis
```

## Diagnóstico

Los registros del sistema (journalctl) muestran:

```bash
cat /var/log/syslog| grep -i redis
...
2025-05-29T06:35:57.647681+02:00 dar redis-server[1064]: 1064:M 29 May 2025 06:35:57.647 * Server initialized
2025-05-29T06:35:57.648969+02:00 dar redis-server[1064]: 1064:M 29 May 2025 06:35:57.648 # Can't handle RDB format version 12
2025-05-29T06:35:57.649027+02:00 dar redis-server[1064]: 1064:M 29 May 2025 06:35:57.648 # Fatal error loading the DB, check server logs. Exiting.
...
```

> Can't handle RDB format version 12
> Fatal error loading the DB, check server logs. Exiting.

Esto indica que Redis no puede leer el archivo de base de datos dump.rdb. Las causas habituales son:
1. El archivo fue generado por una versión de Redis diferente (por ejemplo, tras un downgrade de versión en una actualización de DirectAdmin).
2. El archivo se ha corrompido, posiblemente tras un reinicio forzado o caída del sistema.

## Solución

### Previa para liberar al wordpress del problema

Deberas eliminar el fichero de wordpress `object-cache.php`

```
rm -f /home/USER/domains/DOMAIN.TLD/public_html/wp-content/object-cache.php
```

### Eliminar los archivos dump.rdb afectados para forzar su regeneración limpia:

```bash
rm -f /var/lib/rspamd/.redis/db/dump.rdb
rm -f /home/USER/.redis/db/dump.rdb
```

### Reiniciar los servicios Redis afectados:

```bash
systemctl restart redis-rspamd
systemctl restart redis@USER
```

## Consideraciones

- Esta operación elimina los datos almacenados en caché, pero no afecta a los datos persistentes del sitio WordPress.
- Redis volverá a crear los datos necesarios durante el uso normal del sistema.
- Si Redis estaba siendo utilizado para sesiones, estadísticas u otras funciones de estado, esa información se perderá.

## Verificación

Si no conoces la ubicación del archivo `dump.rdb`, puedes buscarlo con:

```bash
locate dump.rdb
```

> Es necesario tener mlocate o plocate instalado y la base de datos actualizada con updatedb.

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Cómo preparar el acceso a mi terminal desde un cliente SSH externo

- **Aplica a:** cuentas de DirectAdmin con acceso SSH habilitado.
- **Requisito:** que tu cuenta tenga el acceso SSH activado. Si no ves la opción **Terminal** en tu panel, pídenoslo y lo habilitamos.

Nuestros servidores, por seguridad, **no aceptan acceso SSH por contraseña**: solo por **clave pública**. Por eso, si intentas conectar con usuario y contraseña desde un programa externo, verás:

```text
Permission denied (publickey).
```

No es un fallo de tu cuenta: es el servidor pidiéndote tu clave pública. Prepararlo son 5 minutos, se hace **una sola vez**, y ni siquiera necesitas acceso externo para configurarlo: lo haces desde el **Terminal de tu propio panel**.

## Paso 1 — Genera tu par de claves (en tu ordenador)

Una clave SSH tiene dos partes: la **privada** (se queda SIEMPRE en tu ordenador, nunca se comparte) y la **pública** (es la que instalas en el servidor).

### macOS / Linux

En una terminal:

```bash
ssh-keygen -t ed25519 -C "mi-acceso-directadmin"
```

Pulsa Enter para aceptar la ruta por defecto. Puedes poner una *passphrase* (recomendado) o dejarla vacía. Se crean dos ficheros:

- `~/.ssh/id_ed25519` — tu clave **privada** (no la toques ni la envíes a nadie).
- `~/.ssh/id_ed25519.pub` — tu clave **pública** (esta es la que vas a usar).

Para verla y copiarla:

```bash
cat ~/.ssh/id_ed25519.pub
```

### Windows

- **Windows 10/11** ya trae `ssh-keygen`: abre **PowerShell** y ejecuta el mismo comando de arriba. La pública queda en `C:\Users\TU_USUARIO\.ssh\id_ed25519.pub`.
- Si usas **PuTTY**, genera la clave con **PuTTYgen** y copia el recuadro **«Public key for pasting into OpenSSH authorized_keys file»** — ese recuadro ya está en el formato correcto. El fichero `.ppk` **no** sirve para pegar en el servidor.

> **Importante — la clave debe estar en formato OpenSSH.** La clave pública correcta es **una sola línea** que empieza por `ssh-ed25519` o `ssh-rsa`. Si tu clave empieza por `---- BEGIN SSH2 PUBLIC KEY ----`, ese es el formato PuTTY/SSH2 y **no** sirve: usa el recuadro de PuTTYgen indicado arriba.

## Paso 2 — Abre el Terminal de tu panel DirectAdmin

No necesitas acceso SSH externo para este paso: tu panel trae un terminal web integrado.

- Pulsa **Cmd + J** (Mac) o **Ctrl + J** (Windows/Linux) dentro del panel y busca **«Terminal»**.
- O entra directamente por el menú del panel a la sección **Terminal**.

## Paso 3 — Instala tu clave pública

En ese terminal, pega estos comandos — **sustituyendo la línea de ejemplo por TU clave pública** copiada en el Paso 1:

```bash
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'ssh-ed25519 AAAA...TU-CLAVE-PUBLICA-COMPLETA... mi-acceso-directadmin' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
```

Comprueba que ha quedado bien:

```bash
cat ~/.ssh/authorized_keys
```

Debe verse tu clave en **una sola línea**, empezando por `ssh-ed25519` (o `ssh-rsa`).

## Paso 4 — Conéctate desde tu programa de SSH

Ya puedes conectar desde tu terminal o cliente SSH externo:

```bash
ssh -p 2244 TU_USUARIO@TU_SERVIDOR
```

- **TU_USUARIO:** el usuario de tu cuenta de DirectAdmin.
- **TU_SERVIDOR:** el nombre de tu servidor (el que te indicamos en tus datos de acceso).
- **Puerto:** nuestros servidores tienen el SSH en el puerto **2244** (no en el 22 por defecto).

Si pusiste *passphrase* al generar la clave, te la pedirá al conectar: es tuya, no la de la cuenta.

## Problemas frecuentes

- **Sigue diciendo `Permission denied (publickey)`:** revisa que pegaste la clave **pública** (no la privada), en una sola línea, y que `~/.ssh` tiene permisos `700` y `~/.ssh/authorized_keys` permisos `600`.
- **`Connection refused` o se queda colgado:** casi siempre es el puerto — usa **2244**, no el 22.
- **Pegaste una clave `.ppk` o que empieza por `BEGIN SSH2`:** ese no es el formato OpenSSH. Vuelve al Paso 1 (sección Windows / PuTTY).

---

*Última actualización: 2026-07-09.*

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# IP fantasma: un backup en data/admin/ips/ deja nginx y Apache sin arrancar

- **Ámbito:** DirectAdmin (cualquier versión), nginx y/o Apache
- **Autor:** Abdelkarim Mateos
- **Actualizado:** 2026-07-17
- **Gravedad:** alta — el servidor deja de servir TODA la web

## El síntoma

Ni nginx ni Apache arrancan. En el journal:

```text
nginx: [emerg] host not found in "192.0.2.10.bak.20260709" of the "listen" directive
       in /etc/nginx/nginx-vhosts.conf:3

```

```text
AH00526: Syntax error on line 31 of /etc/httpd/conf/extra/httpd-vhosts.conf:
       <VirtualHost> directive missing closing '>'

```

Fíjate en el nombre: `192.0.2.10.bak.20260709`. DirectAdmin está intentando escuchar en una «IP» que en realidad es **el nombre de un fichero de backup**.

Si el monitor de servicios de DirectAdmin está activo, reintentará arrancarlos periódicamente y **enviará un aviso por cada intento fallido**. Eso llega al buzón como «nginx se cae cada hora» y hace pensar en una intermitencia — pero **no es intermitente: es una caída continua**. Cada correo es un reintento, no una caída nueva.

## El mecanismo

**DirectAdmin enumera el directorio `/usr/local/directadmin/data/admin/ips/` tratando CADA NOMBRE DE FICHERO como una entidad IP.** No lee un índice ni valida el formato: lo que hay en ese directorio *es* la lista de IPs del servidor.

Si dejas ahí un backup:

```bash
# /usr/local/directadmin/data/admin/ips/

192.0.2.10                  ← la IP legítima (diradmin:diradmin)

192.0.2.10.bak.20260709     ← un backup: DirectAdmin lo lee como OTRA IP

```

…al generar las configuraciones de nivel servidor concatena ambos «nombres de IP» en el token de la plantilla y produce esto:

```nginx
   server {
        listen       192.0.2.10.bak.20260709

192.0.2.10:80;

        server_name  hostname.ejemplo.com 192.0.2.10.bak.20260709

192.0.2.10;

                proxy_pass http://192.0.2.10.bak.20260709

192.0.2.10:8080;

```

y en Apache un `<VirtualHost>` partido en dos líneas — de ahí el «missing closing `>`».

## Por qué es traicionero: el daño es LATENTE

Este es el punto importante.

`data/admin/ips/` **solo se lee cuando se regeneran las configuraciones de nivel servidor**. Mientras nadie las regenere, el fichero de más convive ahí sin causar ningún daño: **días o semanas**.

La avería aparece cuando algo dispara esa regeneración:

- Una actualización de DirectAdmin.
- Un `./build update_versions`.
- Un `./build rewrite_confs`.
- Cualquier mantenimiento que toque el stack del panel.

**Consecuencia práctica: quien detona la mina no es quien la puso.** El cuadro típico es «hice un mantenimiento rutinario y el servidor se quedó sin web» — y la conclusión fácil («lo rompió la actualización») es falsa: la actualización solo reveló un fichero que llevaba semanas ahí. Si te pasa, **no cierres culpando al mantenimiento**: mira el directorio.

## Detección: cuesta un `ls`

Cualquier nombre en ese directorio que no sea una IPv4 o IPv6 válida es una mina armada:

```bash
ls -1 /usr/local/directadmin/data/admin/ips/ 2>/dev/null \

  | grep -vE '^([0-9]{1,3}\.){3}[0-9]{1,3}$|^[0-9a-fA-F:]+$'
```

- **Salida vacía:** limpio.
- **Cualquier línea:** mina armada — arréglalo **antes** de tocar el servidor.

Merece la pena incorporarlo al pre-flight de cualquier mantenimiento de paquetes o del panel, porque **es precisamente ese mantenimiento el que la detona**. Detectarla después es llegar tarde.

## Solución

```bash
# 1. Retirar el fichero FUERA del árbol de datos (mover, no borrar)
mkdir -p /root/da-stray-backups
mv /usr/local/directadmin/data/admin/ips/192.0.2.10.bak.20260709 /root/da-stray-backups/

# 2. Regenerar las confs de NIVEL SERVIDOR
cd /usr/local/directadmin/custombuild && ./build rewrite_confs

# 3. Verificar sintaxis y arrancar
nginx -t && httpd -t
systemctl start httpd nginx

```

Antes de retirarlo, comprueba que el backup no contiene nada que necesites: lo habitual es que solo difiera en `value=` (el contador de uso que DirectAdmin recalcula solo en el tally nocturno), es decir, que sea **basura sin valor**.

### Trampa: `rewrite_confs` no es lo mismo que la task queue

Este paso se falla a menudo:

- **`echo "action=rewrite&value=httpd" >> data/task.queue` + `dataskq` → NO basta.** Regenera únicamente los vhosts **de usuario** (`data/users/*/nginx.conf`). Tras ejecutarlo, `nginx-vhosts.conf` sigue roto y parece que el arreglo no ha funcionado.
- **Los ficheros de nivel servidor** (`nginx-vhosts.conf`, `httpd-vhosts.conf`) los genera **CustomBuild** → hace falta **`./build rewrite_confs`**.

Y no pierdas el tiempo editando `nginx-vhosts.conf` a mano: **es autogenerado**. Mientras la causa siga en la capa de datos, DirectAdmin lo reescribirá igual de roto en la siguiente pasada.

## Dónde SÍ y dónde NO

No todo backup dentro del árbol de DirectAdmin es una bomba. **Solo `data/admin/ips/` tiene la semántica «un fichero = una entidad»:**

| Directorio | Cómo lo lee DirectAdmin | Un `.bak` ahí es… |
|------------|-------------------------|-------------------|
| `data/admin/ips/` | **enumera por nombre de fichero** | **una bomba** — IP fantasma |
| `conf/` | por nombre exacto (`directadmin.conf`) | inerte (basura) |
| `data/admin/` | por nombre exacto (`cluster.conf`…) | inerte (basura) |
| `data/users/<user>/` | por nombre exacto (`user.conf`…) | inerte (basura) |

Dicho de otro modo: un backup en `conf/` es desorden; un backup en `data/admin/ips/` deja el servidor sin web.

## La regla

**Un backup NUNCA va dentro del árbol de datos del panel.** Va fuera: `/root/`, `/var/backups/`, donde quieras — pero fuera.

Es tentador hacer `cp fichero fichero.bak` en el propio directorio antes de editar algo: es el reflejo de toda la vida y en el 99% de los sitios es inofensivo. En `data/admin/ips/` te cuesta la web del servidor entero, y te la cuesta **semanas después**, cuando ya nadie relaciona una cosa con la otra.

```bash
# MAL — el backup se convierte en una IP fantasma
cp /usr/local/directadmin/data/admin/ips/192.0.2.10{,.bak}

# BIEN — el backup vive fuera del árbol
cp /usr/local/directadmin/data/admin/ips/192.0.2.10 /root/192.0.2.10.bak

```

# Versiones de PHP y php.ini en cuentas con varios dominios

Una cuenta de **Directadmin** puede alojar varios dominios. Cuando se quiere que cada uno use una versión distinta de PHP, o valores distintos de `php.ini`, aparece una confusión muy habitual: **unos ajustes se propagan solos a todos los dominios de la cuenta y otros no**.

No es un fallo del panel. Son dos ámbitos distintos, y conviene tenerlos claros antes de pelearse con la configuración.

## El síntoma típico

Se cambia la versión de PHP y el `memory_limit` en el dominio principal de la cuenta. Al ir al segundo dominio, el panel avisa de que toma la configuración del principal. Y entonces se observa algo aparentemente incoherente:

- El `memory_limit` nuevo **sí** aparece en el segundo dominio, sin haberlo tocado ahí.
- La versión de PHP **no**: el segundo dominio sigue con la versión antigua.

De ahí la sensación de que el panel "no hace caso". Lo que ocurre es que cada cosa se aplica a un nivel diferente.

## Los dos ámbitos

### La versión de PHP es por dominio

Cada dominio guarda su propia selección de versión, de forma independiente. Cambiarla en el dominio principal **no** la cambia en el resto: hay que hacerlo dominio por dominio.

### Los valores del `php.ini` del panel son por cuenta

Directadmin crea **un único pool de PHP-FPM por usuario**, no uno por dominio. Todos los dominios de la cuenta que usen la misma versión de PHP comparten ese pool.

Cuando desde el panel se ajusta `memory_limit`, `upload_max_filesize`, `max_execution_time` y demás, el valor acaba escrito en ese pool como `php_value`. Por eso se aplica a **todos** los dominios de la cuenta a la vez, aunque el formulario se haya abierto teniendo seleccionado un dominio concreto.

## Cambiar la versión de PHP de un dominio

Desde el panel, en modo usuario, en la pantalla de configuración de PHP. El proceso paso a paso, con capturas, está en [Configurar PHP en un servidor con Directadmin](https://wiki.castris.com/books/directadmin/page/configurar-php-en-un-servidor-con-directadmin).

Lo importante:

1. Seleccionar el dominio en el desplegable de la pantalla.
2. Cambiar la versión **de ese dominio**.
3. Repetir con cada dominio que se quiera mover. No basta con el principal.
4. Esperar unos instantes: el panel regenera la configuración del servidor web y recarga PHP-FPM.

## Cómo comprobar de verdad qué versión se está usando

Es la parte donde más se falla, porque hay varias formas de mirar y casi todas engañan.

- **Fiable:** una página en el propio dominio con `phpinfo()`, consultada por navegador, y **borrada inmediatamente después**. Un `phpinfo()` accesible expone la configuración del servidor, así que no puede quedarse ahí.
- **No fiable:** `php -v` por SSH. Devuelve la versión por defecto del servidor o la del usuario, **no** la del dominio, que es la que usa la web.
- **No fiable:** el aviso de versión de PHP del panel de WordPress u otros gestores. Suelen cachear ese dato y tardan en refrescarlo.

## Valores de `php.ini` distintos para cada dominio

Sí es posible, pero **no desde el panel**: requiere acceso de administrador al servidor.

El mecanismo son las secciones `[HOST=]` en un fichero `.ini` dentro de `/usr/local/phpXX/lib/php.conf.d/`:

```ini
[HOST=segundo-dominio.example]
memory_limit = 512M
upload_max_filesize = 128M
```

Los ficheros de ese directorio se cargan por orden alfabético, de ahí que se numeren. Después hay que reconstruir la configuración web:

```bash
da build rewrite_confs
```

Dos detalles verificados que conviene conocer:

- **La sección `[HOST=]` prevalece** sobre el valor que el panel haya dejado en el pool de la cuenta. Es decir, sirve para dar a un dominio concreto un valor distinto del que tiene el resto de la cuenta.
- **No todas las directivas admiten esto.** Las que solo pueden fijarse una vez en el `php.ini` principal, como `disable_functions`, no se pueden sobrescribir por esta vía. Ese caso concreto está tratado en [Cambios especificados en el php.ini disable_functions por dominio](https://wiki.castris.com/books/directadmin/page/cambios-especificados-en-el-phpini-disable-functions-por-dominio-directadmin-example).

Referencia oficial: [Per-domain settings for PHP](https://docs.directadmin.com/webservices/php/php-options.html#how-to-use-per-domain-or-per-path-settings-for-php).

## Resumen

| Qué se quiere cambiar | Ámbito | Quién puede hacerlo |
|---|---|---|
| Versión de PHP | Por dominio | El propio usuario, desde el panel |
| `memory_limit` y similares del panel | Toda la cuenta | El propio usuario, desde el panel |
| `memory_limit` y similares para **un solo** dominio | Por dominio | Solo el administrador del servidor |

##### Aviso

Esta documentación y su contenido no implica que funcione en tu caso o en determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido se entrega tal y como está, sin que ello implique ninguna obligación ni responsabilidad por parte de [Castris](https://castris.com).

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Zona horaria: el servidor va en UTC y la aplicación declara la suya

Nuestros servidores funcionan en **UTC**, y eso es una decisión deliberada, no un descuido de configuración. Cada aplicación alojada declara la zona horaria que necesita.

Es una fuente habitual de confusión, sobre todo en aplicaciones que programan envíos o tareas para una hora concreta: correos que salen dos horas tarde, informes que se generan a destiempo, avisos que llegan cuando ya no tocaba. Esta página explica por qué el servidor va en UTC, y sobre todo **dónde hay que declarar la zona horaria para que el cambio surta efecto**, que casi nunca es donde uno mira primero.

## Por qué el servidor va en UTC

UTC no cambia nunca. No tiene horario de verano, no adelanta ni atrasa, y no depende del país.

Un servidor compartido aloja aplicaciones de clientes que pueden necesitar zonas distintas. Si el servidor se fijara en `Europe/Madrid`, todas heredarían esa zona y las que necesitasen otra tendrían que pelearse contra ella. Además, dos veces al año el reloj daría un salto: en octubre una hora se repite y en marzo una hora no existe. Toda tarea programada en esa franja se ejecuta dos veces o ninguna.

Con el servidor en UTC ese problema desaparece de la base, y cada aplicación resuelve su horario local por su cuenta.

## Hay dos relojes, no uno

Esta es la parte que se pasa por alto y la que explica la mayoría de los casos.

Una aplicación PHP con base de datos tiene **dos relojes independientes**:

- El de **PHP**, que es el que devuelve `date()` y compañía.
- El de **la base de datos**, que es el que devuelve `NOW()` en una consulta SQL.

Cambiar uno **no** cambia el otro. Y muchas aplicaciones no deciden con PHP: deciden con SQL. Cuando una aplicación selecciona qué enviar con una consulta del estilo

```sql
SELECT ... WHERE fecha_programada <= NOW()
```

quien decide la hora es la base de datos. Se puede poner PHP en la zona que se quiera, que el envío seguirá saliendo a la hora antigua.

**Regla práctica:** si lo que falla es *cuándo ocurre algo programado*, sospecha del reloj de la base de datos antes que del de PHP.

## Cómo comprobar cada reloj

El de PHP, desde tu terminal SSH:

```bash
php -r 'echo date("Y-m-d H:i:s T P"), PHP_EOL;'
```

Si usas una versión concreta en el cron, comprueba **esa misma**, invocándola por su ruta completa igual que hace el cron. Distintas versiones de PHP pueden tener configuraciones distintas.

El de la base de datos, desde el cliente MySQL o desde phpMyAdmin:

```sql
SELECT @@session.time_zone, NOW(), UTC_TIMESTAMP();
```

Si `NOW()` y `UTC_TIMESTAMP()` devuelven lo mismo, tu sesión está en UTC.

## Declarar la zona en PHP

Lo correcto es hacerlo **dentro de tu aplicación**, no en la configuración del servidor.

La forma más directa, al principio del arranque de la aplicación:

```php
date_default_timezone_set('Europe/Madrid');
```

Muchas aplicaciones traen ya su propio ajuste de zona horaria en su panel o en su fichero de configuración. **Si existe, úsalo**: suele hacer más cosas que llamar a esa función, como veremos enseguida.

También puedes fijarla para todo un dominio con un fichero `.user.ini` en la raíz del sitio:

```ini
date.timezone = "Europe/Madrid"
```

Ten en cuenta que ese fichero **no afecta a las ejecuciones por línea de comandos**, así que no sirve para tus tareas del cron. Para el cron, o lo declara la aplicación, o se pasa en la propia orden:

```bash
/usr/local/php83/bin/php -d date.timezone=Europe/Madrid /home/<usuario>/domains/<dominio>/public_html/tarea.php
```

### Lo que no hay que hacer

**No intentes modificar el `php.ini` general del servidor.** No vas a poder —tu shell trabaja aislado y esa ruta es de sólo lectura para ti— y, aunque pudieras, sería contraproducente por dos motivos: afectaría a todos los clientes del servidor, y el panel regenera ese fichero en cada actualización de PHP, de modo que el cambio se perdería en la siguiente compilación.

Si te encuentras un mensaje de `Read-only file system` al intentarlo, no es una avería: es el aislamiento previsto de tu cuenta.

## Declarar la zona en la base de datos

Se hace por conexión, con una sentencia al abrirla:

```sql
SET time_zone = 'Europe/Madrid';
```

A partir de ahí, `NOW()` devuelve hora española en esa conexión, y respeta el horario de verano.

Nuestros servidores tienen cargado el catálogo completo de zonas horarias, así que puedes usar nombres como `Europe/Madrid`. Puedes comprobarlo así:

```sql
SET time_zone = 'Europe/Madrid';
SELECT NOW(), UTC_TIMESTAMP();
```

Si las dos horas se diferencian en una o dos horas según la época del año, funciona correctamente.

**Usa siempre el nombre de la zona, nunca un desplazamiento fijo.** Un `SET time_zone = '+02:00'` parece equivalente y no lo es: acertará en verano y fallará en invierno, cuando España está en `+01:00`. El nombre conoce las transiciones; el número, no.

## El caso de phpList

phpList es el ejemplo perfecto de esto, porque decide con SQL y porque su ajuste tiene un nombre que es fácil equivocar.

En su fichero `config/config.php` la constante correcta es:

```php
define('SYSTEM_TIMEZONE', 'Europe/Madrid');
```

Esa constante hace dos cosas a la vez: ajusta el reloj de PHP **y** el de la base de datos. Es exactamente el mecanismo que las alinea, y por eso funciona donde un `date_default_timezone_set()` suelto no llega.

Cuidado con el nombre. `SERVER_TIMEZONE` **no existe** en phpList: si la defines, no da ningún error, simplemente no hace nada, y el problema sigue igual sin ninguna pista de por qué.

## Antes de aplicar el cambio: revisa lo que tengas programado

Este aviso vale para phpList y para cualquier aplicación con tareas o envíos programados.

Al corregir la zona horaria, el reloj con el que la aplicación compara sus fechas **se mueve de golpe**. En España, en horario de verano, avanza dos horas. Eso significa que todo lo que tengas pendiente de salir **se disparará dos horas antes** de lo que la pantalla te indica ahora mismo.

Y hay un caso que conviene mirar con especial atención: si mientras el problema estaba activo compensaste el desfase a mano —programando las cosas dos horas más tarde para que salieran a la hora buena—, esas quedarán **mal en sentido contrario** y saldrán con dos horas de retraso.

La recomendación es sencilla: repasa la lista de tareas o campañas pendientes **antes** de tocar la configuración, y reajusta las que hicieran falta justo después.

## Resumen

- El servidor va en UTC a propósito, y no se cambia.
- La zona horaria la declara tu aplicación, no el servidor.
- Hay dos relojes independientes: el de PHP y el de la base de datos. Cambiar uno no cambia el otro.
- Si lo que falla es *cuándo ocurre* algo programado, mira el reloj de la base de datos.
- Usa siempre nombres de zona (`Europe/Madrid`), nunca desplazamientos fijos (`+02:00`).
- Si tu aplicación trae su propio ajuste de zona, úsalo: normalmente alinea los dos relojes a la vez.
- Revisa lo que tengas programado antes de aplicar el cambio.

Si después de esto sigues viendo un desfase, abre un ticket indicando qué aplicación es, qué hora esperas y qué hora obtienes, y lo revisamos.

# Cuenta por defecto del usuario en Roundcube - Directadmin

## Escenario

El usuario usa la cuenta de usuario del sistema o de hosting `<usuario>` como cuenta de correo de su dominio principal.

Esto suele ocurrir cuando no se usan nombres de usuario aleatorios, para evitar duplicidades, y el usuario usa, si le es válido, el nombre de usuario como cuenta de correo.

Esta cuenta es especial, porque se trata de una cuenta de sistema, y de hecho, su configuración es diferente al resto, pues no tiene que ir acompañada de `@nombredeldominio.tld`.

Sin embargo, esta cuenta por defecto tiene una identidad que no corresponde a una dirección de correo con un dominio [FQDN (Fully Qualified Domain Name)](https://es.wikipedia.org/wiki/FQDN), lo cual en la gran mayoría de los servidores de correo será tratado como `no válida`, por lo que se rechazará la entrega del correo.

## Solución: Configurar la identidad

El problema deriva de la identidad del emisor de la cuenta de correo que no está configurada correctamente.

![Roundcube :: Enviar correo nuevo](https://multimedia.castris.com/imagenes/wiki/directadmin/ROUNDCUBE-IDENTITY-LOCALHOST-01.jpg)

Los pasos son sencillos:
- Configuración 
- Identidades
- Editar la identidad
- Añadir el correo electrónico con el nombre de dominio (FQDN)

![Modificar las identidad en RoundCube](https://multimedia.castris.com/imagenes/wiki/directadmin/ROUNDCUBE-IDENTITY-EDIT-01.jpg)

> Si se indica una identidad con una cuenta de correo electrónico distinta de la cuenta de correo, el servidor puede rechazar su envío, obteniendo un error de servidor debido a la seguridad o al sistema antispam de salida.


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Puesta a punto

Todo lo que te surga para completar, añadir, modificar tu instalación siguiendo las directrices de Directadmin

# Cómo compilar una extensión o módulo de PHP en DirectAdmin

## Introducción

Una buena práctica que sigo en mis proyectos de Laravel, es la de añadir en el `composer.json` del proyecto todo aquello que necesito, inclusive las extensiones, porque no todos los paneles, no todos los SO o no todas las distribuciones, es decir, no todas las instalaciones de de un servidor o equipo de desarrollo, tiene los mismos componentes por defecto.

En el caso de hoy al migrar un despligue de Laravel de cPanel a un DirectAdmin, cuando fui ha hacer un `composer update` salto un error indicándome la necesidad de tener la extensión `pcntl`.

Gracias a las buenas prácticas y los principios SOLID de programación, es fácil evitar un disgusto de proque no funciona, y donde esta el fallo.

### Composer.json
```json
{
    ...
    "require": {
        "php": "^8.1",
        "ext-pdo": "*",
        "ext-redis": "*",
        "ext-zlib": "*",
        "ext-pcntl": "*",
        ...
```        


## Como instalar la extensión en PHP en un servidor Directadmin

DirectAdmin tiene u número determinado de extensiones que desde la administración se pueden incluir en todas las instalaciones o desactivarlas. Pero también tiene u mecanismo sencillo para incorporar módulos de PHP a nuestra instalación, definidos en su documentación, [Add a custom module to PHP Cuastombuild](https://docs.directadmin.com/webservices/php/php-extensions.html#add-a-custom-module-to-php-with-custombuild)

### Localizar las configuraciones de PHP

```bash
da build used_configs | grep 'configure\.php'
PHP 8.3 configuration file: /usr/local/directadmin/custombuild/custom/php/configure.php83
PHP 8.2 configuration file: /usr/local/directadmin/custombuild/custom/php/configure.php82
PHP 5.6 configuration file: /usr/local/directadmin/custombuild/configure/php/configure.php56
PHP 7.4 configuration file: /usr/local/directadmin/custombuild/configure/php/configure.php74
```

### Usar el override de Directadmin 

```bash
VERSION=82
cd /usr/local/directadmin/custombuild
mkdir -p custom/php
cp -fp "configure/php/configure.php$VERSION" "custom/php/configure.php$VERSION"
```

Después deberás añadir el código necesario en el fichero `custom/php/configure.php$VERSION`, para que en la compilación de PHP se añade el módulo.

Verificamos:

```
cat  configuration file: /usr/local/directadmin/custombuild/custom/php/configure.php82
...
	--enable-mbstring \
	--enable-intl \
   --enable-pcntl
```       

Puedes usar esto mismo para usarlos en tantas versiones como desees. 

> Ahora DirectAdmin ya soporta hasta 8 versiones de PHP pero recuerda, que no es buena idea añadirlo a todo por muchas razones. Carga innecesaria, versiones antiguas que igual no lo soporte, y forzar al usuario a que actualice su versión a las actuales.

### Completar el trabajo

Compilar
```
da build php
```

Reiniciar

```
systemctl restart httpd
systemctl restart nginx // Si no usas httpd
systemctl restart php-fpm$VERSION
```


##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Configuración de Headers de Seguridad en Nginx con DirectAdmin

## Introducción

Esta guía documenta cómo configurar headers de seguridad en servidores con DirectAdmin, tanto a nivel global como para dominios específicos que requieran exclusiones.

**Última actualización: 25/12/2024 GMT+1**
## Configuración Global

La configuración global se aplica a **todos los dominios** del servidor mediante el archivo:

```
/usr/local/directadmin/data/templates/custom/cust_nginx.CUSTOM.post

```

### Crear el directorio custom (si no existe)

```bash
mkdir -p /usr/local/directadmin/data/templates/custom

```

### Ejemplo de configuración global

```nginx
# Security Headers - Global
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-src 'self' https://www.youtube.com https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;

# Custom error pages for nginx-only domains
error_page 400 /error/400.html;
error_page 401 /error/401.html;
error_page 403 /error/403.html;
error_page 404 /error/404.html;
error_page 405 /error/405.html;
error_page 406 /error/406.html;
error_page 413 /error/413.html;
error_page 422 /error/422.html;
error_page 429 /error/429.html;
error_page 431 /error/431.html;
error_page 500 /error/500.html;
error_page 502 /error/502.html;
error_page 503 /error/503.html;
error_page 504 /error/504.html;

location ^~ /error/ {
    alias |DOCROOT|/error/;
    internal;
    # Repetir headers en este location block
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; frame-src 'self' https://www.youtube.com https://www.google.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
}

```

### Aplicar cambios

Después de crear o modificar el archivo, reconstruir las configuraciones de nginx:

```bash
cd /usr/local/directadmin/custombuild
./build rewrite_confs

```

## Personalización por Dominio

Para dominios que requieren configuraciones diferentes (CSP más permisivo, exclusiones de headers, etc.), DirectAdmin permite crear archivos de personalización por dominio.

### Ubicación de archivos

```
/usr/local/directadmin/data/users/<USUARIO>/domains/<DOMINIO>.cust_nginx
/usr/local/directadmin/data/users/<USUARIO>/domains/<DOMINIO>.cust_nginx.2
/usr/local/directadmin/data/users/<USUARIO>/domains/<DOMINIO>.cust_nginx.3
/usr/local/directadmin/data/users/<USUARIO>/domains/<DOMINIO>.cust_nginx.4

```

### Archivo de configuración personalizada en home del usuario

Para configuraciones más complejas, se puede crear un archivo `.conf` en el directorio del usuario e incluirlo:

```
/home/<USUARIO>/nginx/<nombre>.conf

```

## Puntos de Inserción en DirectAdmin

DirectAdmin utiliza tokens en la plantilla `nginx_server.conf` para insertar configuraciones personalizadas:

| Token | Archivo por Dominio | Ubicación en nginx |
|-------|--------------------|--------------------|
| `CUSTOM1` | `.cust_nginx` | Antes del bloque `server {}` |
| `CUSTOM` | (interno) | Inicio del bloque `server {}` |
| `CUSTOM2` | `.cust_nginx.2` | Dentro de `location / {}` |
| `CUSTOM3` | `.cust_nginx.3` | Después de locations, antes de webapps |
| `CUSTOM4` | `.cust_nginx.4` | Final del bloque `server {}` |

### Archivo .cust_nginx (CUSTOM1)

Se usa principalmente para:

- Cambiar el DOCROOT (aplicaciones Laravel, etc.)
- Configuraciones que van antes del bloque server

Ejemplo - Cambiar DOCROOT para Laravel:

```nginx
|?DOCROOT=`HOME`/domains/`DOMAIN`/mi-app/public|
```

### Archivo .cust_nginx.3 (CUSTOM3)

Se usa para:

- Configuraciones de cache
- Location blocks adicionales
- Headers específicos para tipos de archivo

Ejemplo - Cache de assets:

```nginx
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico)$ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}
location ~* \.(jpg|jpeg|gif|png|svg)$ {
    expires 365d;
}
location ~* \.(pdf|css|html|js|swf)$ {
    expires 2d;
}

```

### Archivo .cust_nginx.4 (CUSTOM4)

Se usa para:

- Incluir archivos de configuración externos
- Location blocks complejos
- Reglas de rewrite específicas

Ejemplo - Incluir configuración WHMCS:

```nginx
include /home/intranet/nginx/whmcs.conf;

```

## Ejemplos Prácticos

### Ejemplo 1: Dominio con CSP relajado

Para un dominio que necesita permitir scripts de terceros (ej: pasarelas de pago, widgets):

Archivo: `/usr/local/directadmin/data/users/usuario/domains/tienda.com.cust_nginx.4`

```nginx
# Sobrescribir CSP global con uno más permisivo
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: blob:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https: blob:; font-src 'self' data: https:; connect-src 'self' https: wss:; frame-src 'self' https:; frame-ancestors 'self'; base-uri 'self'; form-action 'self' https:;" always;

```

### Ejemplo 2: Dominio que permite iframes externos

Para un dominio que necesita ser embebido en otros sitios:

Archivo: `/usr/local/directadmin/data/users/usuario/domains/widget.com.cust_nginx.4`

```nginx
# Permitir que el sitio sea embebido
add_header X-Frame-Options "" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors https://sitio-padre.com https://otro-sitio.com;" always;

```

### Ejemplo 3: Aplicación Laravel con DOCROOT personalizado

Archivo: `/usr/local/directadmin/data/users/develop/domains/larafactu.com.cust_nginx`

```nginx
|?DOCROOT=`HOME`/domains/`DOMAIN`/larafactu/public|
```

### Ejemplo 4: BookStack Wiki

Archivo: `/usr/local/directadmin/data/users/castris/domains/wiki.castris.com.cust_nginx`

```nginx
|?DOCROOT=`HOME`/domains/`DOMAIN`/BookStack/public|
```

### Ejemplo 5: Configuración compleja con archivo externo

Crear archivo de configuración en home del usuario:

Archivo: `/home/intranet/nginx/whmcs.conf`

```nginx
# WHMCS CONFIG
location ~ /announcements/?(.*)$ {
    rewrite ^/(.*)$ /index.php?rp=/announcements/$1;
}

location ~ /download/?(.*)$ {
    rewrite ^/(.*)$ /index.php?rp=/download$1;
}

location ~ /knowledgebase/?(.*)$ {
    rewrite ^/(.*)$ /index.php?rp=/knowledgebase/$1;
}

# ... más reglas de rewrite ...

# Security Advisory
location ^~ /vendor/ {
    deny all;
    return 403;
}

```

Archivo: `/usr/local/directadmin/data/users/intranet/domains/intranet.castris.com.cust_nginx.4`

```nginx
include /home/intranet/nginx/whmcs.conf;

```

### Ejemplo 6: Deshabilitar todos los headers de seguridad (NO RECOMENDADO)

Solo para debugging o situaciones muy específicas:

Archivo: `/usr/local/directadmin/data/users/usuario/domains/debug.com.cust_nginx.4`

```nginx
# TEMPORAL - Solo para debugging
add_header X-Content-Type-Options "" always;
add_header X-Frame-Options "" always;
add_header X-XSS-Protection "" always;
add_header Referrer-Policy "" always;
add_header Permissions-Policy "" always;
add_header Strict-Transport-Security "" always;
add_header Content-Security-Policy "" always;

```

## Troubleshooting

### Verificar sintaxis de nginx

```bash
nginx -t

```

### Ver configuración generada para un dominio

```bash
cat /etc/nginx/conf.d/usuario.dominio.com.conf

```

### Reconstruir todas las configuraciones

```bash
cd /usr/local/directadmin/custombuild
./build rewrite_confs

```

### Recargar nginx sin reiniciar

```bash
systemctl reload nginx

```

### Verificar headers de un sitio

```bash
curl -I https://dominio.com

```

### Logs de errores

```bash
tail -f /var/log/nginx/domains/dominio.com.error.log

```

### Notas importantes

1. **Orden de precedencia**: Los headers definidos en location blocks más específicos sobrescriben los globales.

2. **Keyword always**: Usar siempre `always` al final del `add_header` para que se aplique en todas las respuestas (incluyendo errores).

3. **Reconstruir después de cambios**: Siempre ejecutar `./build rewrite_confs` después de modificar archivos `.cust_nginx*`.

4. **Permisos**: Los archivos deben tener permisos correctos para que DirectAdmin pueda leerlos.

5. **Variables de DirectAdmin**: Se pueden usar variables como `|DOCROOT|`, `|DOMAIN|`, `|HOME|` en las configuraciones.

## Variables Disponibles en Templates

| Variable | Descripción |
|----------|-------------|
| `\|DOMAIN\|` | Nombre del dominio |
| `\|HOME\|` | Directorio home del usuario |
| `\|DOCROOT\|` | Document root del dominio |
| `\|IP\|` | IP del servidor |
| `\|PORT_80\|` | Puerto HTTP |
| `\|PORT_443\|` | Puerto HTTPS |

## Referencias

- [DirectAdmin Custom Nginx Templates](https://docs.directadmin.com/webservices/nginx/nginx_templates.html)
- [Mozilla Observatory](https://observatory.mozilla.org/) - Para verificar headers de seguridad
- [Security Headers](https://securityheaders.com/) - Análisis de headers

##### Aviso

Esta documentación se entrega tal y como está, basada en configuración del servidor dar.tabratino.com.

# DMARC masivo — da_dmarc_bulk.sh

## Descripción

Script para crear registros `_dmarc` TXT en todos los dominios de un servidor DirectAdmin que no lo tengan. Usa la API local de DA (`CMD_API_DNS_CONTROL`), que propaga automáticamente los cambios a los miembros del cluster DNS (titrit/amazzal).

## Ubicación

```
/root/utilidades/directadmin/da_dmarc_bulk.sh
```

Repositorio: `~/utilidades/` (git)

## Uso

```bash
# Ver qué haría sin hacer cambios
./da_dmarc_bulk.sh --dry-run

# Ejecutar en todos los dominios
./da_dmarc_bulk.sh

# Solo un dominio específico
./da_dmarc_bulk.sh --domain ejemplo.com

# Con política quarantine en vez de none
./da_dmarc_bulk.sh --policy quarantine

# Con dirección RUA personalizada (%DOMAIN% se sustituye por el dominio)
./da_dmarc_bulk.sh --rua "postmaster@%DOMAIN%"
```

## Opciones

| Opción | Descripción | Default |
|--------|-------------|---------|
| `--dry-run` | Muestra qué haría sin hacer cambios | — |
| `--policy` | Política DMARC: `none`, `quarantine`, `reject` | `none` |
| `--rua` | Email para reportes agregados. `%DOMAIN%` = placeholder | `dmarc@%DOMAIN%` |
| `--domain` | Procesar solo este dominio | todos |

## Requisitos

- Root en servidor DirectAdmin
- `curl`, `dig`, `python3`
- DirectAdmin binary en `/usr/local/directadmin/directadmin`

## Cómo funciona

1. Obtiene credenciales API via `directadmin api-url`
2. Lista usuarios y dominios desde el filesystem (`/usr/local/directadmin/data/users/*/domains/*.conf`)
3. Para cada dominio, consulta `dig +short _dmarc.DOMAIN TXT @127.0.0.1`
4. Si no tiene DMARC, lo crea via `CMD_API_DNS_CONTROL` con impersonación (`admin|usuario`)
5. El cluster DNS sincroniza automáticamente

## Detalles técnicos

### API DirectAdmin para DNS

- **Endpoint:** `CMD_API_DNS_CONTROL?domain=DOMINIO&json=yes`
- **Impersonación obligatoria:** `admin|usuario:password` — el admin no puede modificar DNS de un dominio directamente, debe impersonar al usuario propietario
- **Formato del value TXT:** las comillas deben ir URL-encoded (`%22`) dentro del valor: `%22v%3DDMARC1...%22`
- **Propagación:** DA incrementa el serial de la zona y los slaves sincronizan via AXFR. Si no sincronizan inmediatamente, ejecutar `rndc notify DOMINIO` en el master

### Ficheros que NO son dominios

El script filtra automáticamente ficheros `.hotlink`, `.suspended` y `.letsencrypt` que DA almacena en el mismo directorio que los `.conf` de dominios.

### Dominios con DNS externo

Dominios cuyo DNS no está en el servidor DA (ej: Cloudflare) darán error `Domain does not belong to you` o `DNS controlled remotely`. Es esperado — esos DMARC hay que crearlos manualmente en el panel DNS externo.

## Ejemplo de salida

```
============================================
 DirectAdmin Bulk DMARC Creator
============================================
 Policy:  none
 RUA:     dmarc@%DOMAIN%
============================================

[OK] elayudante.es (user: elayudante) — DMARC created
[OK] fontaneriafontcal.com (user: ffontcal) — DMARC created
[SKIP] alufasa.com — DMARC already exists
[ERROR] status.castris.com (user: laravel): DNS controlled remotely

============================================
 Results
============================================
 Total domains:  91
 Created:        87
 Already exist:  3
 Errors:         1
============================================
```

## Ejecución inicial (2026-03-12)

| Servidor | Total | Creados | Ya existían | Errores |
|----------|-------|---------|-------------|--------|
| kvm456 | 91 | 87 | 4 | 0 |
| dar | 19 | 15 | 4 | 0 |
| srv120 | 276 | 275 | 0 | 1 (.hotlink) |
| srv121 | 139 | 76 | 62 | 1 (.hotlink) |
| amazzal | 2 | 0 | 1 | 1 (DNS externo) |
| titrit | 0 | 0 | 0 | 0 |

## Registro DMARC creado

```
_dmarc  1800  IN  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@DOMINIO"
```

- **`p=none`** — modo monitorización (no rechaza ni pone en cuarentena). Recomendado como primer paso
- **`rua`** — dirección donde llegan los reportes agregados. Requiere que exista el buzón `dmarc@dominio` (o redirección)

## Licencia

AGPL-3.0 — GNU Affero General Public License v3.0

# Desactivar logs de PHP en sitio PHP-FPM de DirectAdmin

### Introducción

Este procedimiento permite **desactivar la escritura de logs de errores de PHP** en sitios gestionados con **PHP-FPM en DirectAdmin**. Aunque es aplicable en otros entornos, esta guía está enfocada específicamente para servidores con **DirectAdmin**.

- Muchos usuarios **ignoran repetidamente las advertencias** que reciben desde el servicio técnico sobre errores PHP en sus sitios.
- En sitios con errores recurrentes y falta de mantenimiento, **la escritura constante en los logs representa un gasto innecesario de disco y rendimiento**.
- Al desactivar los logs, **se reduce drásticamente la capacidad de soporte y diagnóstico** por parte de los administradores. El usuario debe ser consciente de esta limitación.

### Qué se configura

El objetivo es indicar a PHP-FPM que **no registre errores y que descarte cualquier intento de escribir en los logs**, modificando las siguientes directivas:

```ini
php_admin_value[log_errors] = Off
php_admin_value[error_log] = /dev/null
```

Estas directivas deben aplicarse en el contexto del **pool de PHP-FPM** correspondiente al usuario.

### Opción recomendada en DirectAdmin

En DirectAdmin, como **administrador**, debes seguir esta ruta:
- Escritorio > Configuraciones HTTPD personalizadas > Configuración PHP-FPM (Dashboard > Custom HTTPD Configurations > PHP-FPM Configuration)

![PHP-FPM Configuracion](https://multimedia.castris.com/imagenes/wiki/directadmin/php-fpm-per-user-php_ini.jpg)

Una vez dentro, busca la sección correspondiente al dominio del usuario y versión PHP-FPM usada.

En la pestaña “php-fpm Global |CUSTOM2|”, añade las siguientes líneas:

```
php_admin_value[log_errors] = Off
php_admin_value[error_log] = /dev/null
```

- Esta modificación aplica a nivel de configuración avanzada del pool de PHP-FPM del dominio.
- Asegúrate de hacerlo correctamente para evitar interrupciones del servicio PHP.

No olvides hacer clic en el botón **DA BUILD REWRITE_CONFS** para aplicar los cambios.

![DA BUILD REWRITE_CONFS](https://multimedia.castris.com/imagenes/wiki/directadmin/configuraciones_httpd_personalizadas_da_build_rewrite_confs.jpg)

### Alternativa genérica (para conocimiento)

En entornos sin interfaz como DirectAdmin, puedes aplicar lo mismo editando directamente el archivo del pool correspondiente:

```
sudo nano /usr/local/php82/etc/php-fpm.d/USERNAME_O_DOMAIN.conf
```

Y añadiendo:

```ini
php_admin_value[log_errors] = Off
php_admin_value[error_log] = /dev/null
```

Luego, reinicia PHP-FPM:

```bash
sudo systemctl restart php-fpm82
```

## Referencia oficial
- [Documentación de DirectAdmin sobre PHP-FPM y php.ini por usuario](https://docs.directadmin.com/webservices/php/php-fpm.html#per-user-php-ini-settings-in-php-fpm)

## Ejemplo de mensaje común por saturación de logs

> Notice: Log /var/log/httpd/domains/DOMAIN_NAME_error.log has been rotated due to oversize

Este tipo de mensajes reflejan claramente la necesidad de intervenir en sitios donde el error no es resuelto y los logs crecen sin control.

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Mejoras sobre Directadmin

A veces hay que tunear o mejorar el software contenido y hay que hacerlo basándose en el paradigma del panel.

# Whitelist dinámica de rspamd via email en Directadmin

## Qué hace

Permite a cualquier usuario autenticado del servidor gestionar la whitelist de rspamd enviando un email. No requiere acceso al panel ni SSH.

**Enviar un email a `whitelist@<cualquier-dominio-del-servidor>`** con la acción en el asunto:

| Asunto del email | Acción |
|---|---|
| `add ejemplo.com` | Añade el dominio completo ejemplo.com — todos los correos de @ejemplo.com pasan |
| `add user@ejemplo.com` | Añade solo esa dirección concreta — otros de @ejemplo.com siguen filtrados |
| `ejemplo.com` | Añade ejemplo.com (add implícito) |
| `del ejemplo.com` | Elimina ejemplo.com de la whitelist |
| `del user@ejemplo.com` | Elimina esa dirección de la whitelist |
| `remove ejemplo.com` | Igual que `del` (también `delete`, `rm`) |
| `list` | Responde con la whitelist completa actual |

El sistema responde por email confirmando cada operación: dominio añadido, eliminado, ya existente, bloqueado, o error de formato.

El email debe enviarse desde un cliente de correo autenticado (Thunderbird, Outlook, webmail). Correos no autenticados son ignorados silenciosamente.

## Dominios vs direcciones

El sistema distingue entre dos tipos de whitelist:

- **Dominio completo** (`add ejemplo.com`): todos los correos desde cualquier cuenta @ejemplo.com pasan sin penalización
- **Dirección concreta** (`add user@ejemplo.com`): solo los correos de esa cuenta pasan; el resto de @ejemplo.com sigue siendo evaluado normalmente

Esto es útil para proveedores masivos (Gmail, Outlook, Proton, etc.) donde no queremos whitelistear todo el dominio sino solo una cuenta concreta de confianza.

## Restricciones de seguridad

- **Solo SMTP AUTH:** si el email no viene de un usuario autenticado del servidor, se descarta
- **Mass providers bloqueados como dominio:** no se puede whitelistear gmail.com, outlook.com, proton.me, etc. como dominio completo — pero SÍ se puede whitelistear una dirección concreta como `contacto@proton.me`
- **Límite:** máximo 500 entradas por servidor (dominios + direcciones combinados)
- **Validación estricta:** el dominio/email debe tener formato válido
- **Respuesta informativa:** el sistema responde indicando exactamente qué hizo o por qué rechazó la petición

### Proveedores masivos bloqueados

gmail.com, googlemail.com, yahoo.com, yahoo.es, yahoo.fr, hotmail.com, hotmail.es, outlook.com, outlook.es, live.com, msn.com, aol.com, mail.ru, yandex.ru, yandex.com, protonmail.com, proton.me, icloud.com, me.com, mac.com, gmx.com, gmx.es, gmx.de, zoho.com, tutanota.com, tuta.com, tuta.io, fastmail.com, mail.com, email.com, web.de, freenet.de, t-online.de, libero.it, virgilio.it, laposte.net, free.fr, orange.fr, 163.com, 126.com, qq.com

Si se intenta añadir uno de estos como dominio, el sistema lo rechaza y sugiere usar la dirección completa (`add usuario@proveedor.com`).

## Cómo funciona internamente

```
Email autenticado → whitelist@dominio.com
        ↓
Exim router (whitelist_pipe_router)
  · condición: $authenticated_id no vacío
  · domains = +local_domains (solo dominios hospedados)
        ↓
Exim transport → pipe a /usr/local/bin/rspamd_whitelist.sh
  · pasa AUTH_USER via variable de entorno
        ↓
Script: parsea Subject, valida, determina tipo (dominio/dirección)
        ↓
  Si dominio → /etc/rspamd/local.d/maps/whitelist_dynamic.map
  Si dirección → /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
        ↓
rspamd recarga automáticamente (~10 segundos)
        ↓
  Multimap WHITELIST_FROM_DYNAMIC (dominios, score -100.0)
  Multimap WHITELIST_ADDR_DYNAMIC (direcciones, score -100.0)
        ↓
Respuesta por email al usuario confirmando la operación
```

## Ficheros involucrados

| Fichero | Propósito | Persistencia |
|---|---|---|
| `/usr/local/bin/rspamd_whitelist.sh` | Script principal | Manual (no gestionado por DA) |
| `/etc/exim.routers.pre.conf` | Router Exim custom | Sobrevive `rewrite_confs` (include en template DA) |
| `/etc/exim.transports.pre.conf` | Transport Exim custom | Sobrevive `rewrite_confs` (include en template DA) |
| `/etc/rspamd/local.d/multimap.conf` | Definición de los símbolos WHITELIST_*_DYNAMIC | `local.d/` = override oficial de rspamd |
| `/etc/rspamd/local.d/maps/whitelist_dynamic.map` | Lista de dominios (uno por línea) | Fichero de datos, no config |
| `/etc/rspamd/local.d/maps/whitelist_dynamic_addr.map` | Lista de direcciones exactas (una por línea) | Fichero de datos, no config |
| `/var/log/rspamd_whitelist.log` | Log de operaciones | Fichero de log |

## Persistencia ante rebuilds de DA

Los ficheros `.pre.conf` son puntos de extensión del template de Exim en DirectAdmin. El template fuente (`custombuild/configure/exim/exim.conf`) contiene:

```
.include_if_exists /etc/exim.routers.pre.conf     # línea 519
.include_if_exists /etc/exim.transports.pre.conf   # línea 773
```

Estas directivas `.include_if_exists` están en el template, no solo en el fichero generado. Esto significa que:

- `build rewrite_confs` regenera `/etc/exim.conf` pero **no toca** los `.pre.conf`
- `build exim_conf` idem
- `build exim` (rebuild completo del binario) idem

Los ficheros `.pre.conf` son del usuario, no de DA. Son el único mecanismo ligero para añadir routers y transports custom sin mantener una copia completa de exim.conf.

Para rspamd, `local.d/` es el [mecanismo oficial de override](https://docs.rspamd.com/faq/#what-is-the-difference-between-locald-and-overrided). DA no gestiona estos ficheros.

## Instalación en un servidor nuevo

### Prerequisitos

- DirectAdmin con Exim y rspamd
- rspamd con multimap configurado (al menos `local.d/multimap.conf` existente)

### Pasos

**1. Script:**

```bash
# Subir rspamd_whitelist.sh al servidor
scp rspamd_whitelist.sh root@servidor:/usr/local/bin/
chmod +x /usr/local/bin/rspamd_whitelist.sh
```

**2. Map files y log:**

```bash
# Map de dominios
touch /etc/rspamd/local.d/maps/whitelist_dynamic.map
chown root:mail /etc/rspamd/local.d/maps/whitelist_dynamic.map
chmod 664 /etc/rspamd/local.d/maps/whitelist_dynamic.map

# Map de direcciones
touch /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
chown root:mail /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
chmod 664 /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map

# Log
touch /var/log/rspamd_whitelist.log
chown mail:mail /var/log/rspamd_whitelist.log
chmod 644 /var/log/rspamd_whitelist.log
```

**3. Exim router** (prepend a `/etc/exim.routers.pre.conf` — ANTES de cualquier otro router):

```
# Dynamic whitelist via email
whitelist_pipe_router:
  driver = accept
  local_parts = whitelist
  domains = +local_domains
  condition = ${if !eq{$authenticated_id}{}}
  transport = whitelist_pipe_transport
```

**4. Exim transport** (crear o añadir a `/etc/exim.transports.pre.conf`):

```
# Dynamic whitelist pipe transport
whitelist_pipe_transport:
  driver = pipe
  command = /usr/local/bin/rspamd_whitelist.sh
  user = mail
  environment = AUTH_USER=$authenticated_id
```

**5. rspamd multimap** (añadir a `/etc/rspamd/local.d/multimap.conf`):

```ucl
WHITELIST_FROM_DYNAMIC {
    type = "from";
    filter = "email:domain";
    map = "/etc/rspamd/local.d/maps/whitelist_dynamic.map";
    symbol = "WHITELIST_FROM_DYNAMIC";
    score = -100.0;
    description = "Dynamic whitelist — added via email by authenticated users";
}

WHITELIST_ADDR_DYNAMIC {
    type = "from";
    filter = "email:addr";
    map = "/etc/rspamd/local.d/maps/whitelist_dynamic_addr.map";
    symbol = "WHITELIST_ADDR_DYNAMIC";
    score = -100.0;
    description = "Dynamic whitelist (address) — specific sender addresses added via email";
}
```

**6. Verificar y reiniciar:**

```bash
exim -bV                    # Syntax check Exim
rspamadm configtest         # Syntax check rspamd
systemctl restart exim
systemctl restart rspamd
systemctl is-active exim rspamd
```

### Verificación

```bash
# Comprobar que el router es el primero
exim -bP router_list | head -5

# Comprobar que rspamd carga los símbolos
rspamadm configdump multimap | grep -A3 "WHITELIST.*DYNAMIC"

# Test de routing (debe resolver al pipe transport)
exim -bt whitelist@undominiodel.servidor
```

## Operaciones de mantenimiento

**Ver whitelist actual:**

```bash
echo "=== Dominios ===" && cat /etc/rspamd/local.d/maps/whitelist_dynamic.map
echo "=== Direcciones ===" && cat /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
```

**Ver log de operaciones:**

```bash
cat /var/log/rspamd_whitelist.log
```

**Añadir/eliminar manualmente:**

```bash
# Añadir dominio
echo "dominio.com" >> /etc/rspamd/local.d/maps/whitelist_dynamic.map

# Añadir dirección
echo "user@dominio.com" >> /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map

# Eliminar (dominios)
sed -i '/^dominio\.com$/d' /etc/rspamd/local.d/maps/whitelist_dynamic.map

# Eliminar (direcciones)
sed -i '/^user@dominio\.com$/d' /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
```

rspamd recarga el map file automáticamente en ~10 segundos.

**Vaciar whitelist:**

```bash
> /etc/rspamd/local.d/maps/whitelist_dynamic.map
> /etc/rspamd/local.d/maps/whitelist_dynamic_addr.map
```

## Trampas conocidas

- **Lock file:** NO usar `/var/lock/` ni `/run/lock/` — están montados con `noexec` y bash no puede abrir file descriptors ahí. El script usa el LOG file como target de `flock` (advisory lock, no interfiere con appends)
- **Permisos map files:** deben ser `root:mail 664`. Si se crean como root durante tests, el transport Exim (que corre como `user = mail`) no podrá escribir. Verificar con `ls -la /etc/rspamd/local.d/maps/whitelist_dynamic*.map`
- **El directorio `/etc/rspamd/local.d/maps/` es `root:root 755`:** mail no puede crear ficheros nuevos ahí. El script escribe directamente al map file (que sí tiene permiso de grupo). La operación `del` usa redirección en memoria en vez de fichero temporal
- **Tests manuales como root:** crean lock/map/log files con ownership `root:root`. Tras tests manuales, verificar y corregir permisos: `chown root:mail /etc/rspamd/local.d/maps/whitelist_dynamic*.map`

## Servidores desplegados

| Servidor | Fecha | Estado |
|---|---|---|
| kvm456 | 2026-03-11 | Activo, probado (v3: dominios + direcciones + respuesta email) |
| srv120 | 2026-03-11 | Activo |
| srv121 | 2026-03-11 | Activo |
| dar | 2026-03-11 | Activo |

**No aplica:** titrit (no recibe correo), amazzal (no recibe correo)

# Tuning de rspamd en DirectAdmin — Guía post-instalación

## Por qué hace falta

DirectAdmin instala rspamd via CustomBuild pero lo entrega **sin configuración operativa**:

- **Bayes:** backend SQLite (lento, sin concurrencia), `per_user=true` (cada usuario necesita 200+ muestras propias — inalcanzable en hosting compartido), sin autolearn, sin entrenamiento
- **Thresholds:** defaults muy permisivos (reject=15, add_header=6)
- **ESF (Easy Spam Fighter):** otorga -60 puntos acumulados a cualquier correo con SPF+DKIM+rDNS válidos — blinda spam reenviado via Google Groups
- **Sin phishing.conf:** OpenPhish desactivado por defecto desde rspamd 3.14.3
- **Sin url_suspect.conf:** patrones de URL ofuscada pueden generar falsos positivos en footers HTML

**Resultado:** rspamd funciona solo con reglas estáticas, Bayes nunca contribuye, y un spam con autenticación válida llega al INBOX.

## Ficheros a crear

La receta completa son **8 ficheros** en `/etc/rspamd/local.d/` (que DA nunca toca) + 1 en ESF:

### 1. `/etc/rspamd/local.d/actions.conf` — Thresholds globales

```hcl
# Thresholds globales rspamd
# reject = null → NUNCA rechazar correo (solo marcar)
# add_header = 5 → marca como spam a partir de score 5

reject = null;
add_header = 5;
rewrite_subject = 8;
greylist = null;
```

**Decisión clave:** `reject = null`. NUNCA rechazar correo por score rspamd en hosting compartido. El riesgo de rechazar un falso positivo legítimo es mayor que el coste de marcar. El usuario puede revisar su carpeta spam.

### 2. `/etc/rspamd/local.d/classifier-bayes.conf` — Bayes con Redis

```hcl
# Bayes con Redis — obligatorio para rendimiento y concurrencia
# per_user = false: pool global, obligatorio para <50 usuarios

backend = "redis";
autolearn = true;

autolearn {
    spam_threshold = 8.0;
    ham_threshold = -1.0;
    min_balance = 0.9;
    min_learns = 100;
}

per_user = false;

# Conexión Redis — DA usa socket, NO localhost:6379
servers = "/var/lib/rspamd/.redis/redis.sock";
```

**Trampas:**

- **`per_user = false` es obligatorio** para servidores <50 usuarios. Con `per_user=true`, cada usuario necesita 100+ spam y 100+ ham propios — nunca se alcanza
- **Redis en DA no es el estándar.** El servicio se llama `redis-rspamd.service`, no `redis-server`. El socket está en `/var/lib/rspamd/.redis/redis.sock`
- **Verificar Redis:** `systemctl is-active redis-rspamd.service`

### 3. `/etc/rspamd/local.d/groups-override.conf` — Neutralizar MAILLIST

```hcl
# Neutralizar símbolo MAILLIST — beneficia spam via Google Groups
symbols {
    "MAILLIST" {
        weight = 0.0;
        description = "Neutralized - mailing lists benefit spam forwarding";
    }
}
```

### 4. `/etc/rspamd/local.d/multimap.conf` — Penalizaciones y whitelists

```hcl
# Penalizar mailing lists de dominios sospechosos
FOREIGN_MAILLIST {
    type = "header";
    header = "List-Id";
    map = "/etc/rspamd/local.d/maps/foreign_maillist_domains.map";
    regexp = true;
    score = 4.0;
    description = "Foreign mailing list domain in List-Id";
}

GOOGLE_GROUPS_FOREIGN {
    type = "header";
    header = "X-Beenthere";
    map = "/etc/rspamd/local.d/maps/foreign_maillist_domains.map";
    regexp = true;
    score = 3.0;
    description = "Google Groups with foreign domain in X-Beenthere";
}

# Whitelist de dominios confianza
WHITELIST_FROM {
    type = "from";
    filter = "email:domain";
    map = "/etc/rspamd/local.d/maps/whitelist_from.map";
    symbol = "WHITELIST_FROM_TRUSTED";
    score = -100.0;
    description = "Whitelisted trusted sender — domain match";
}
```

**Trampas multimap:**

- **`filter = "email:domain"` es OBLIGATORIO** para whitelists por dominio. Sin él, rspamd matchea la dirección completa contra el literal del mapa — nunca coincide
- **Dominios sin `@` en el mapa** — `castris.com`, NO `@castris.com`
- **NUNCA usar `prefilter=true` + `action="no action"` como whitelist** — no funciona de forma fiable. Usar `score = -100.0` es la forma correcta
- **`regexp:///path`** como protocolo de mapa no funciona — usar `map = "/path"; regexp = true;` como campos separados

### 5. `/etc/rspamd/local.d/maps/foreign_maillist_domains.map`

```
# Dominios de mailing lists sospechosos (regex)
/go1001000\.com/
/googlegroups\.com/
```

Añadir nuevos dominios según aparezcan en spam. rspamd recarga mapas automáticamente (~10s).

### 6. `/etc/rspamd/local.d/maps/whitelist_from.map`

```
# Dominios de confianza (uno por línea, sin @)
castris.com
aichadigital.es
tabratino.com
```

### 7. `/etc/rspamd/local.d/phishing.conf` — Activar OpenPhish

```hcl
# OpenPhish desactivado por defecto desde rspamd 3.14.3
# Activar explícitamente para detección de phishing
openphish_enabled = true;
```

### 8. `/etc/rspamd/local.d/url_suspect.conf` — Desactivar word_dot

```hcl
# El patrón word_dot genera falsos positivos en footers HTML
# (ej: "reservados. skyp" → URL_OBFUSCATED_TEXT score 9.0)
# Afecta correos WHMCS, PHPMailer, cualquier footer con "frase. Dominio"
checks {
    obfuscated_text {
        patterns_enabled {
            word_dot = false;
        }
    }
}
```

**Contexto:** `URL_OBFUSCATED_TEXT` es un prefilter con score 5.0 hardcoded en `url_suspect.lua`. Ni `groups-override.conf` ni `override.d/` pueden cambiar el peso. La única solución es desactivar el patrón causante.

### 9. `/etc/exim.easy_spam_fighter/variables.conf.custom` — Reducir scores ESF

```
# Reducir scores ESF de -60 acumulado a -15
# El default blinda spam con autenticación válida (SPF+DKIM+rDNS = -60)
EASY_SPF_PASS == -5
EASY_DKIM_PASS == -5
EASY_FORWARD_CONFIRMED_RDNS == -5

# No rechazar correo por score ESF (nunca)
EASY_HIGH_SCORE_DROP == 9999

# Escanear correos hasta 15MB (default 200K omite adjuntos)
EASY_SPAMASSASSIN_MAX_SIZE == 15M

# Penalizaciones reducidas (evitar falsos positivos por forwarding)
EASY_DKIM_FAIL == 0
EASY_NO_REVERSE_IP == 30
EASY_SPF_FAIL == 50
EASY_SPF_SOFT_FAIL == 10
```

**Trampas ESF:**

- **Usar `==` para redefinir macros**, no `=`. Con `=` simple, Exim falla: "macro already defined (use ==)"
- **`variables.conf.custom` se incluye via `.include_if_exists`** al final de `variables.conf`. DA regenera `variables.conf` pero NUNCA toca el `.custom`

## DNS resolvers — CRÍTICO para servidores de correo

rspamd consulta listas de reputación (Spamhaus ZEN, SURBL, URIBL) via DNS. Si el resolver está bloqueado, todas estas listas devuelven "not found" y el spam pasa sin penalización.

**Resolvers válidos para DNSBL:**

| Resolver | IP | Spamhaus |
|----------|----|----------|
| Verisign | 64.6.64.6 | ✅ |
| Neustar | 199.85.126.10 | ✅ |
| Neustar Ultra | 156.154.70.2 | ✅ |

**NUNCA usar en servidores de correo:**

| Resolver | IP | Spamhaus |
|----------|----|----------|
| Google | 8.8.8.8 | ❌ Bloqueado |
| Cloudflare | 1.1.1.1 | ❌ Bloqueado |
| Quad9 | 9.9.9.9 | ❌ Bloqueado |
| OVH | 213.186.33.99 | ❌ Bloqueado |

**Verificación:**

```bash
# Debe devolver 127.0.0.2, 127.0.0.4 o 127.0.0.10
dig +short 2.0.0.127.zen.spamhaus.org

# Si devuelve NXDOMAIN o 127.255.255.254 → resolver bloqueado
```

**Síntomas de DNSBL ciegas en rspamd:** símbolos `DBL_BLOCKED_OPENRESOLVER`, `URIBL_BLOCKED`, `SURBL_BLOCKED` en las cabeceras X-Spamd-Result.

**Cambiar DNS en Ubuntu (netplan):**

```bash
# Editar /etc/netplan/*.yaml → nameservers: [64.6.64.6, 199.85.126.10, 156.154.70.2]
netplan apply
systemctl restart systemd-networkd
systemctl restart systemd-resolved
# Verificar: dig +short 2.0.0.127.zen.spamhaus.org
```

**Atención:** `netplan apply` puede no aplicar DNS — requiere reiniciar `systemd-networkd` y `systemd-resolved` por separado.

## Entrenamiento masivo de Bayes

Sin entrenamiento, Bayes no contribuye al scoring (necesita mínimo 200 muestras de cada clase).

```bash
# SPAM — desde carpetas spam de todos los usuarios
# IMPORTANTE: usar SIEMPRE controller socket, NO worker 11333
find /home/*/Maildir/.INBOX.spam/cur/ -type f 2>/dev/null | head -500 | \
  xargs -I{} rspamc --connect /var/run/rspamd/rspamd_controller.sock learn_spam {}

find /home/*/Maildir/.Junk/cur/ -type f 2>/dev/null | head -500 | \
  xargs -I{} rspamc --connect /var/run/rspamd/rspamd_controller.sock learn_spam {}

# HAM — desde INBOX y Sent de todos los usuarios
find /home/*/Maildir/cur/ -type f 2>/dev/null | head -500 | \
  xargs -I{} rspamc --connect /var/run/rspamd/rspamd_controller.sock learn_ham {}

find /home/*/Maildir/.Sent/cur/ -type f 2>/dev/null | head -500 | \
  xargs -I{} rspamc --connect /var/run/rspamd/rspamd_controller.sock learn_ham {}
```

**Trampas rspamc:**

- **`rspamc learn_spam` contra el worker (puerto 11333)** da "HTTP 500 invalid command". HAY QUE usar el controller socket
- **`rspamc stat` contra puerto default (11333)** da "Connection refused" en DA — usar siempre: `rspamc --connect /var/run/rspamd/rspamd_controller.sock stat`
- **`redis-server` / `redis`** no existen como servicio en DA — es `redis-rspamd.service`
- **`exim -bP macro`** (singular) no existe — el comando es `exim -bP macros` (plural)

**Verificación post-training:**

```bash
rspamc --connect /var/run/rspamd/rspamd_controller.sock stat | grep -E "learned|messages"
# Debe mostrar >200 spam y >200 ham
```

## Procedimiento de aplicación

### Pre-checks

```bash
systemctl is-active rspamd
systemctl is-active redis-rspamd.service
ls -la /etc/rspamd/local.d/
cat /etc/exim.easy_spam_fighter/variables.conf.custom 2>/dev/null || echo "NO EXISTE"
# Contar correos disponibles para training
find /home/*/Maildir/.INBOX.spam/cur/ -type f 2>/dev/null | wc -l
find /home/*/Maildir/.Junk/cur/ -type f 2>/dev/null | wc -l
```

### Orden

1. `mkdir -p /etc/rspamd/local.d/maps`
2. Crear los 8 ficheros rspamd (secciones 1-8 arriba)
3. `rspamadm configtest` — verificar sintaxis
4. `systemctl reload rspamd`
5. Crear/actualizar `variables.conf.custom` (sección 9)
6. `exim -bV` — verificar sintaxis Exim
7. `systemctl restart exim`
8. Entrenamiento masivo de Bayes
9. Verificar: `rspamc stat`, enviar correo de prueba

### Verificación post-aplicación

```bash
# rspamd
systemctl is-active rspamd
rspamadm configtest
rspamc --connect /var/run/rspamd/rspamd_controller.sock stat

# Exim
systemctl is-active exim
exim -bP macros | grep -E "EASY_(SPF|DKIM|FORWARD)"
# Debe mostrar -5 en cada macro

# Test correo: enviar email y verificar cabeceras X-Spamd-Result
```

## Rollback

Todos los ficheros son independientes. Para revertir cualquier cambio, borrar el fichero y `systemctl reload rspamd`:

```bash
# Rollback total rspamd
rm -f /etc/rspamd/local.d/actions.conf
rm -f /etc/rspamd/local.d/classifier-bayes.conf
rm -f /etc/rspamd/local.d/groups-override.conf
rm -f /etc/rspamd/local.d/multimap.conf
rm -f /etc/rspamd/local.d/phishing.conf
rm -f /etc/rspamd/local.d/url_suspect.conf
rm -rf /etc/rspamd/local.d/maps/
systemctl reload rspamd
```

Para ESF, editar `variables.conf.custom` y eliminar las líneas añadidas, luego `systemctl restart exim`.

## Servidores aplicados

| Servidor | Fecha | Bayes (spam/ham) | Estado |
|----------|-------|------------------|--------|
| kvm456 | 2026-03-04 | 1.035 / 873 | Completo |
| dar | 2026-03-04 (alineado 2026-03-23) | Trained | Completo |
| srv120 | 2026-03-07 | 567 / 448 | Completo |
| srv121 | 2026-03-07 | 158 / 1.597 | Autolearn activo (spam bajo) |

## Ficheros que DA gestiona — NO TOCAR

| Fichero | Motivo |
|---------|--------|
| `/etc/rspamd/users.d/<user>.conf` | Settings per-user generados por DA |
| `/etc/rspamd/directadmin-users.conf` | Config global usuarios DA |
| `/etc/exim.easy_spam_fighter/variables.conf` | Macros ESF (el `.custom` SÍ es seguro) |
| `/etc/rspamd/local.d/dkim_signing.conf` | Puede ser gestionado por DA |

# Bug template rspamd_settings.conf — Whitelist per-user ineficaz

## El problema

Las whitelists de SpamAssassin que los usuarios configuran desde el panel de DirectAdmin (E-Mail → SpamAssassin Setup → Whitelist) **no funcionan** con rspamd. Los remitentes whitelisteados siguen llegando a la carpeta spam.

Afecta a **todas las versiones de DirectAdmin con `spamd=rspamd`** (verificado hasta DA 1.696).

## Causa raíz (doble)

El template `/usr/local/directadmin/data/templates/rspamd_settings.conf` genera bloques `_whitelist` en `/etc/rspamd/users.d/*.conf` con dos defectos:

### 1. Priority incorrecta

El bloque `_whitelist` tiene `priority = 4` hardcoded, pero el bloque `_prefs` usa `priority = medium` (= 5 en rspamd). rspamd selecciona el bloque de mayor prioridad → `_prefs` SIEMPRE gana, la whitelist NUNCA se aplica.

### 2. Falta `apply.actions`

Incluso si la prioridad fuera correcta, el bloque whitelist solo tiene `want_spam = yes` sin un bloque `apply { actions { ... } }`. rspamd sigue añadiendo cabecera `X-Spam-Status: Yes` y el filtro de dominio de Exim sigue enviando a la carpeta spam.

### Contraste con blacklist

Los bloques `_blacklist` del mismo template **sí funcionan** porque:

- Usan `priority = high` (= 10)
- Incluyen `apply { actions { ... } }`

## Cómo verificar

```bash
# Ver qué settings block aplica rspamd para un usuario
grep "settings.lua" /var/log/rspamd/rspamd.log | grep USUARIO

# Siempre mostrará "settings_id: USUARIO_prefs" — nunca "USUARIO_whitelist"
```

## Solución: template custom

### Paso 1: Crear copia custom del template

```bash
mkdir -p /usr/local/directadmin/data/templates/custom/
cp /usr/local/directadmin/data/templates/rspamd_settings.conf \
   /usr/local/directadmin/data/templates/custom/rspamd_settings.conf
```

### Paso 2: Editar la copia custom

En `/usr/local/directadmin/data/templates/custom/rspamd_settings.conf`, buscar los bloques que contienen `whitelist` y cambiar:

**Antes (roto):**

```
|*if whitelist_count>"0"|
|WHITELIST_ID| {
	priority = 4;
|CUSTOM12|
|RCPT|
|whitelist_from_list|
	want_spam = yes;
|CUSTOM13|
}
```

**Después (corregido):**

```
|*if whitelist_count>"0"|
|WHITELIST_ID| {
	priority = 6;
|CUSTOM12|
|RCPT|
|whitelist_from_list|
	want_spam = yes;
	apply {
		actions {
			"add header" = 9999;
			"rewrite subject" = null;
		}
	}
|CUSTOM13|
}
```

Hay **dos bloques** whitelist en el template (uno con `|whitelist_from_list|` y otro con listas adicionales). Aplicar el cambio a ambos.

### Paso 3: Regenerar users.d para usuarios existentes

El template custom se aplica cuando:

- Un usuario cambia sus preferencias de spam en el panel
- Se ejecuta un rebuild que afecta a users.d

Para forzar la regeneración de un usuario específico, el usuario debe ir a E-Mail → SpamAssassin Setup y guardar (aunque no cambie nada).

Para **usuarios que ya tenían whitelist activa**, hay que parchear manualmente su fichero `users.d/*.conf`:

```bash
# Ejemplo para usuario "alufasa" en kvm456
# En /etc/rspamd/users.d/alufasa.conf, buscar el bloque _whitelist y cambiar:
# priority = 4 → priority = 6
# Añadir apply.actions tras want_spam = yes
```

### Cadena de prioridades correcta

```
blacklist (high=10) > whitelist (6) > prefs (medium=5) > default
```

## Persistencia

El template custom en `data/templates/custom/` sobrevive updates de DirectAdmin. Es el mecanismo oficial de override.

## Referencia: post en el foro DA

Este bug fue reportado en el foro oficial de DirectAdmin:

[Bug: rspamd_settings.conf template — per-user whitelist never applied (priority 4 < prefs medium)](https://forum.directadmin.com/threads/bug-rspamd_settings-conf-template-per-user-whitelist-never-applied-priority-4-prefs-medium.82089/)

El bug original de blacklist (mismo template) fue reportado en 2019 y corregido. La whitelist nunca recibió el mismo fix.

## Alternativa: whitelist dinámica via email

Independientemente de este bug, existe un sistema de whitelist dinámica gestionado via email que **SÍ funciona correctamente** — usa multimap con score -100 en vez del mecanismo de settings per-user. Ver la página "Whitelist dinámica de rspamd via email en DirectAdmin" en esta misma wiki.

## Servidores con template custom desplegado

| Servidor | Fecha | Usuarios parcheados manualmente |
|----------|-------|---------------------------------|
| kvm456 | 2026-03-19 | alufasa, amenocalbus, audionica, elayudante, gestemalisal, lopeza, regfiltersl |
| srv120 | 2026-03-19 | tmar |
| srv121 | 2026-03-19 | neolystic |
| dar | 2026-03-19 | (ninguno con whitelist activa) |

## Nota sobre `want_spam = yes`

`want_spam = yes` en rspamd settings NO es una whitelist por sí solo. Solo indica que el bloque de settings se aplica también a mensajes clasificados como spam. Sin `apply.actions`, rspamd sigue marcando el correo como spam. Es una trampa sutil de la documentación de rspamd.

# CSF + ModSecurity en nginx: MODSEC_LOG y regex custom

## El problema

En servidores DirectAdmin con **nginx + ModSecurity 3** (no Apache), CSF/LFD no detecta los bloqueos de ModSecurity. Un usuario puede ser bloqueado repetidamente por ModSecurity (HTTP 406) sin que LFD lo registre ni tome acción (ban temporal, notificación, etc.).

Consecuencia: ModSecurity bloquea requests pero LFD es **ciego** a esos eventos. Si una IP llega a `csf.deny`, es por otro mecanismo (DA BFM, manual), nunca por `LF_MODSEC`.

## Causa raíz (triple)

### 1. `MODSEC_LOG` apunta al fichero equivocado

El default de CSF es:

```
MODSEC_LOG = "/var/log/httpd/error_log"
```

Pero en DirectAdmin con nginx + ModSecurity 3, **ModSecurity corre en nginx, no en Apache**. Los eventos de "Access denied" se escriben en los **error.log per-domain de nginx**, no en httpd:

```
/var/log/nginx/domains/dominio.com.error.log
```

### 2. CSF no tiene regex para nginx-modsec3

El fichero built-in `RegexMain.pm` solo tiene regex para el formato ModSecurity v2 (Apache). El formato nginx-modsecurity3 tiene campos extra (`PID#TID: *CONN`) que la regex no matchea.

**Formato Apache-modsec2:**

```
[date] [error] [client IP] ModSecurity: Access denied ...
```

**Formato nginx-modsec3:**

```
YYYY/MM/DD HH:MM:SS [error] PID#TID: *CONN [client IP] ModSecurity: Access denied ...
```

### 3. Logs distribuidos por dominio

Los "Access denied" de nginx-modsec3 se escriben en los error.log per-domain de nginx, no en un fichero centralizado. CSF necesita leer TODOS los error.log de dominio.

## Solución

### Paso 1: Corregir `MODSEC_LOG` en `csf.conf`

```bash
# En /etc/csf/csf.conf, cambiar:
MODSEC_LOG = "/var/log/nginx/domains/*.error.log"
```

CSF soporta globs — se evalúan al iniciar LFD. Cada `*.error.log` de cada dominio será monitoreado.

**Consecuencia:** tras crear un nuevo dominio, hay que reiniciar LFD (`csf -ra`) para que monitorice su error.log nuevo.

### Paso 2: Crear regex custom para nginx-modsec3

En `/usr/local/csf/bin/regex.custom.pm`, añadir **dentro de la sección de reglas** (antes del `return ()` final):

```perl
# nginx-modsecurity3 format
# YYYY/MM/DD HH:MM:SS [error] PID#TID: *CONN [client IP] ModSecurity: Access denied ...
if (($globlogs{MODSEC_LOG}{$lgfile}) and ($line =~ /^\S+ \S+ \[\S+\] \S+ \*\d+ \[client (\S+)\] ModSecurity:(( \[[^\]]+\])*)? Access denied/)) {
    my $ip = $1;
    my $domain = "";
    if ($line =~ /\[hostname "([^"]+)"\]/) {$domain = $1}
    my $ruleid = "unknown";
    if ($line =~ /\[id "(\d+)"\]/) {$ruleid = $1}
    $ip =~ s/^::ffff://;
    return ("mod_security v3 (id:$ruleid domain:$domain) triggered by", $ip, "modsecnginx", "5", "80,443", "7200", "0");
}
```

**Qué hace:**

- Matchea el formato nginx-modsec3
- Extrae IP, dominio (hostname), y rule ID
- Retorna al LFD con trigger type "modsecnginx", 5 hits para ban, puertos 80/443, ban temporal de 2 horas

### Paso 3: Verificar sintaxis y reiniciar

```bash
# Verificar que el Perl compila
perl -c /usr/local/csf/bin/regex.custom.pm

# Reiniciar CSF + LFD
csf -ra
```

### Paso 4: Verificar que LFD monitoriza los ficheros

```bash
# Debe mostrar líneas de "watching" para *.error.log
grep "watching" /var/log/lfd.log | grep nginx | tail -10
```

## Persistencia

- **`regex.custom.pm` sobrevive upgrades de CSF** — es el lugar oficial para customizaciones
- **`csf.conf`** también sobrevive upgrades, pero verificar tras actualizaciones mayores de CSF
- **Los globs en `MODSEC_LOG` solo se evalúan al iniciar LFD** — tras crear un nuevo dominio, reiniciar LFD con `csf -ra`

## Diagnóstico

Si sospechas que LFD no está detectando bloqueos ModSecurity:

```bash
# 1. Verificar que hay eventos ModSecurity en los logs nginx
grep "ModSecurity: Access denied" /var/log/nginx/domains/*.error.log | tail -5

# 2. Verificar que MODSEC_LOG apunta a nginx
grep "MODSEC_LOG" /etc/csf/csf.conf

# 3. Verificar que LFD registra triggers modsec
grep "modsec" /var/log/lfd.log | tail -10

# 4. Si no hay nada en lfd.log pero sí hay eventos en nginx:
# → MODSEC_LOG incorrecto o regex no matchea
```

## Servidores aplicados

| Servidor | Fecha | Estado |
|----------|-------|--------|
| srv120 | 2026-03-05 | Activo |
| srv121 | 2026-03-05 | Activo |
| kvm456 | 2026-03-05 | Activo |
| amazzal | 2026-03-05 | Activo |
| dar | 2026-03-05 | Activo |
| titrit | N/A | No tiene modsec en nginx |

## Checklist para nuevos servidores DA

- [ ] `MODSEC_LOG` en csf.conf apunta a `/var/log/nginx/domains/*.error.log`
- [ ] `regex.custom.pm` tiene la regex para nginx-modsec3
- [ ] `perl -c regex.custom.pm` compila sin errores
- [ ] `csf -ra` ejecutado
- [ ] Verificar con `grep "watching" /var/log/lfd.log | grep nginx`

# Arquitectura antispam en DirectAdmin: Exim ESF + rspamd

## Las tres capas

DirectAdmin con rspamd activo procesa cada correo entrante en tres capas secuenciales:

```
Correo entrante
    │
    ▼
┌──────────────────────────┐
│  1. Exim ACL (ESF)       │  Easy Spam Fighter: SPF, DKIM, RBL, rDNS
│     Score acumulado en    │  Variables: acl_m_easy69 (score), acl_c_esf_skip (bypass)
│     acl_m_easy69          │  Bypass: esf_skip_recipients, esf_skip_senders
└──────────┬───────────────┘
           │
           ▼
┌──────────────────────────┐
│  2. rspamd scan          │  Bayes, fuzzy hashes, URL reputation, phishing,
│     Score rspamd          │  DKIM/SPF/DMARC (independiente de ESF),
│     independiente         │  multimap, settings per-user
└──────────┬───────────────┘
           │
           ▼
┌──────────────────────────┐
│  3. Exim filter          │  Per-domain: whitelist/blacklist del usuario,
│     Entrega final         │  entrega a inbox o carpeta spam según score combinado
└──────────────────────────┘
```

**Punto clave:** ESF y rspamd son sistemas **independientes**. ESF evalúa en la ACL de Exim (antes de aceptar el correo), rspamd evalúa el contenido (después de aceptar). Cada uno genera su propio score. La decisión final la toma el filtro de dominio de Exim combinando ambos.

## Capa 1: Easy Spam Fighter (ESF)

ESF es un conjunto de ACLs de Exim mantenido por DirectAdmin. Evalúa:

| Check | Macro (default) | Qué hace |
|-------|-----------------|----------|
| SPF pass | `EASY_SPF_PASS` (-30) | Bonificación por SPF válido |
| DKIM pass | `EASY_DKIM_PASS` (-20) | Bonificación por DKIM válido |
| rDNS confirmado | `EASY_FORWARD_CONFIRMED_RDNS` (-10) | Bonificación por PTR válido |
| SPF fail | `EASY_SPF_FAIL` (100) | Penalización por SPF inválido |
| DKIM fail | `EASY_DKIM_FAIL` (100) | Penalización por DKIM inválido |
| Sin rDNS | `EASY_NO_REVERSE_IP` (100) | Penalización por sin PTR |
| RBL hit | Variable | Penalización por blacklist |
| **Acumulado default** | **-60** | SPF+DKIM+rDNS válidos |

### El problema del -60

Un correo spam reenviado via Google Groups pasa SPF (google.com) + DKIM (google.com) + rDNS → obtiene **-60 puntos ESF** antes de que rspamd lo evalúe. Si rspamd devuelve un score bajo (no action), ESF no marca el correo y se entrega al INBOX.

**Solución aplicada:** reducir a -15 total (-5/-5/-5) via `variables.conf.custom`. Ver la página "Tuning de rspamd en DirectAdmin" para detalles.

### Dónde se configuran los overrides ESF

```
/etc/exim.easy_spam_fighter/variables.conf         ← DA regenera (NO TOCAR)
/etc/exim.easy_spam_fighter/variables.conf.custom   ← Override seguro (== para redefinir)
```

## Capa 2: rspamd

rspamd evalúa el contenido del correo de forma independiente:

- Bayes (aprendizaje estadístico)
- Fuzzy hashes (contenido conocido como spam)
- URL reputation (Spamhaus DBL, SURBL, URIBL)
- Phishing detection (OpenPhish)
- DKIM/SPF/DMARC (evaluación independiente de ESF)
- Multimap (whitelists, blacklists, penalizaciones custom)
- Settings per-user (thresholds, whitelists del panel)

**Ficheros de configuración:**

| Ruta | Gestión | Seguro para editar |
|------|---------|-------------------|
| `/etc/rspamd/local.d/*.conf` | Manual | ✅ Sí |
| `/etc/rspamd/local.d/maps/*.map` | Manual | ✅ Sí |
| `/etc/rspamd/users.d/*.conf` | DirectAdmin | ❌ No |
| `/etc/rspamd/directadmin-users.conf` | DirectAdmin | ❌ No |

## Punto de unión: la condición de escaneo rspamd

El módulo rspamd de Exim (`/etc/exim/rspamd/check_message.conf`) tiene esta condición antes de escanear:

```
warn  condition = ${if eq{$acl_c_rspamd}{1}}
      condition = ${if !eq{$acl_c_esf_skip}{1}}      ← bypass ESF = bypass rspamd
      condition = ${if < {$message_size}{EASY_SPAMASSASSIN_MAX_SIZE}}
      condition = ${if !eq{$acl_m_spam_user}{nobody}}
      set acl_m_rspamd_on = 1
```

**Cuatro condiciones deben cumplirse para que rspamd escanee:**

1. `acl_c_rspamd = 1` — rspamd habilitado globalmente
2. `acl_c_esf_skip ≠ 1` — el dominio/remitente NO está en `esf_skip_*`
3. `message_size < MAX_SIZE` — correo dentro del límite (default 200K, recomendado 15M)
4. `acl_m_spam_user ≠ nobody` — el usuario tiene `~/.spamassassin/user_prefs`

Si **cualquiera** falla, rspamd NO escanea y el correo pasa sin evaluación de contenido.

## Mecanismos de bypass

### `esf_skip_recipients` y `esf_skip_senders`

Son **bypass TOTALES** — desactivan tanto el scoring ESF como el escaneo rspamd.

```
/etc/virtual/esf_skip_recipients   ← dominios destino (uno por línea)
/etc/virtual/esf_skip_senders      ← dominios remitente (uno por línea)
```

Cuando un dominio está en estos ficheros, `acl_c_esf_skip` se pone a 1 y rspamd no escanea.

**Cuándo se usan:** Como medida de protección para dominios con historial de falsos positivos graves que generaron fricción con el cliente. Es una decisión operativa consciente, no un mecanismo de "no rechazar".

**Riesgo:** Los correos que pasan por bypass no tienen NINGÚN escaneo — ni ESF ni rspamd ni phishing ni malware.

### `acl_m_spam_user = nobody`

Si `~/.spamassassin/user_prefs` no existe para un usuario DA, la ACL no puede resolver el usuario y rspamd no escanea. Esto afecta silenciosamente a usuarios que nunca han tocado la configuración de SpamAssassin en el panel.

**Diagnóstico:**

```bash
# Contar correos sin escanear por user_prefs faltante
grep "acl_m_spam_user=nobody" /var/log/exim/mainlog | wc -l

# Listar usuarios sin user_prefs
for d in /home/*/; do
    user=$(basename "$d")
    if [ ! -f "$d/.spamassassin/user_prefs" ]; then
        echo "$user"
    fi
done
```

**Solución:** Crear `user_prefs` vacío con propietario correcto:

```bash
for d in /home/*/; do
    user=$(basename "$d")
    uid=$(id -u "$user" 2>/dev/null) || continue
    prefs_dir="$d/.spamassassin"
    prefs_file="$prefs_dir/user_prefs"
    if [ ! -f "$prefs_file" ]; then
        mkdir -p "$prefs_dir"
        touch "$prefs_file"
        chown -R "$user:$user" "$prefs_dir"
    fi
done
```

### `EASY_SPAMASSASSIN_MAX_SIZE`

Default: 200K. Correos con adjuntos (>200K) no se escanean. Recomendado: 15M.

```
# En variables.conf.custom
EASY_SPAMASSASSIN_MAX_SIZE == 15M
```

## Alternativas a bypass total

Para casos donde se necesita proteger contra falsos positivos SIN perder el escaneo rspamd:

| Mecanismo | Qué hace | rspamd escanea | Riesgo |
|-----------|----------|---------------|--------|
| `esf_skip_*` | Bypass total | ❌ No | Sin protección alguna |
| `whitelist_from.map` (score -100) | Whitelist rspamd | ✅ Sí | Bajo (detecta phishing/malware) |
| Whitelist dinámica (via email) | Whitelist rspamd | ✅ Sí | Bajo |
| Per-user threshold alto | Threshold permisivo | ✅ Sí | Medio (score alto pasa) |

**Recomendación:** Para nuevos casos de "no rechazar", usar `whitelist_from.map` con score -100. rspamd escanea (phishing, malware, URLs), pero el score se neutraliza.

## Diagrama de decisión: ¿por qué no se escaneó?

```
¿rspamd escaneó este correo?
    │
    ├── NO → ¿acl_c_esf_skip = 1?
    │         ├── SÍ → Dominio en esf_skip_recipients o esf_skip_senders
    │         └── NO → ¿message_size > MAX_SIZE?
    │                   ├── SÍ → Correo demasiado grande (subir MAX_SIZE)
    │                   └── NO → ¿acl_m_spam_user = nobody?
    │                             ├── SÍ → user_prefs no existe (crear)
    │                             └── NO → ¿acl_c_rspamd = 0?
    │                                       ├── SÍ → rspamd desactivado globalmente
    │                                       └── NO → Investigar ACLs
    │
    └── SÍ → ¿Por qué pasó?
              ├── Score bajo → Revisar Bayes, multimap, thresholds
              ├── Whitelist activa → Revisar whitelist_from.map + dynamic
              └── ESF score muy negativo → Reducir scores ESF (variables.conf.custom)
```

## Diagnóstico rápido

```bash
# 1. ¿rspamd escaneó? Buscar cabeceras en el correo
grep "X-Spamd-Result" /path/to/email

# 2. ¿Cuántos correos no se escanearon esta semana?
# Por esf_skip:
grep "esf_skip" /var/log/exim/mainlog | wc -l

# Por user nobody:
grep "spam_user=nobody" /var/log/exim/mainlog | wc -l

# Por tamaño:
grep "too large for spam" /var/log/exim/mainlog | wc -l

# 3. ¿Qué dominios tienen bypass activo?
cat /etc/virtual/esf_skip_recipients
cat /etc/virtual/esf_skip_senders

# 4. Estado de Bayes (usar controller socket en DA)
rspamc --connect /var/run/rspamd/rspamd_controller.sock stat

# 5. Volumen spam vs ham
rspamc --connect /var/run/rspamd/rspamd_controller.sock stat | grep -E "Messages scanned|Spam|Ham"
```

## Resumen de configuración de referencia

Tras aplicar el tuning completo documentado en esta wiki:

| Componente | Configuración | Fichero |
|------------|--------------|---------|
| ESF scores | -5/-5/-5 (total -15) | `variables.conf.custom` |
| ESF max_size | 15M | `variables.conf.custom` |
| ESF high_score_drop | 9999 (nunca) | `variables.conf.custom` |
| rspamd reject | null (nunca) | `actions.conf` |
| rspamd add_header | 5 (marcar) | `actions.conf` |
| Bayes backend | Redis (pool global) | `classifier-bayes.conf` |
| Bayes autolearn | spam ≥8.0, ham ≤-1.0 | `classifier-bayes.conf` |
| Phishing | OpenPhish activo | `phishing.conf` |
| URL suspect | word_dot desactivado | `url_suspect.conf` |
| Whitelist estática | score -100, filter=email:domain | `multimap.conf` |
| Whitelist dinámica | Via email, score -100 | `multimap.conf` |
| DNSBL resolvers | 64.6.64.6 / 199.85.126.10 / 156.154.70.2 | netplan |

# DirectAdmin startips — supervisor / auto-restart de IPs virtuales

**Fecha:** 2026-04-30
- **Autor:** Abdelkarim Mateos
- **Estado:** plan de desarrollo, NO IMPLEMENTADO. Reservado para sesión futura.
- **Origen:** incidente Burcode 2026-04-30 (cloud500 IP virtual `87.98.230.68` caída → diagnóstico erróneo y reescritura de 178 A records → reversión).
- **Informe relacionado:** `informes/2026-04/2026-04-30-burcode-cloud500-fail500-dns-glue-muerto.md`
- **Memory:** `~/.claude/projects/-Users-abkrim-claude/memory/feedback-da-startips-no-protegido.md`

## 1. Problema

`startips.service` en DirectAdmin es `Type=oneshot`:

```ini
[Unit]
Description=Start the additional IPs
Wants=network-online.target
After=syslog.target network.target network-online.target
Requires=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/directadmin/scripts/startips

[Install]
WantedBy=multi-user.target
```

El binario `/usr/local/directadmin/scripts/startips` recorre `/usr/local/directadmin/data/admin/ip.list` y aplica IPs adicionales con `ip addr add` sobre la interfaz primaria. Tras correr, el servicio queda `inactive (dead)` con `code=exited, status=0/SUCCESS`. **No hay supervisor.**

Si las IPs virtuales se caen tras boot por cualquier motivo:

- Reset/restart del subsistema de red (`systemctl restart networkd`, `netplan apply`, mantenimiento del proveedor).
- OOM killing de `networkd`.
- vRack/cloud-init re-aplicando configuración fresca.
- Cambio en `ens3` que limpia los aliases.

…las IPs no vuelven hasta el siguiente reboot completo o hasta que alguien lance `systemctl start startips.service` o `bash /usr/local/directadmin/scripts/startips` manualmente.

**Impacto operativo:**

- Dominios DA con virtual host ligado a IP virtual quedan inalcanzables.
- LE auto-renew falla para esos dominios (HTTP-01 challenge no responde).
- Diagnóstico desde fuera puede confundirse con "servidor antiguo desmantelado" (el operador o un agente terminan reescribiendo DNS por error — caso real Burcode 2026-04-30).

El operador reporta que es **problema recurrente** en su parque DA — segundo frente de trabajo abierto.

## 2. Diseño propuesto

### 2.1 Componentes

#### A) Health-check periódico de IPs virtuales

Script en `~/utilidades/directadmin/check-virtual-ips.sh` (versionado en monorepo `gitlab.castris.com/root/utilidades`):

- Lee `/usr/local/directadmin/data/admin/ip.list`.
- Para cada IP, comprueba si está activa en la interfaz primaria (`ip -4 addr show <iface> | grep <ip>`).
- Si UNA IP de la lista NO está en la interfaz, lanza:

  1. `systemctl start startips.service` (idempotente — el servicio ya existe).
  2. Verifica que tras el restart la IP aparece.
  3. Si tras el restart sigue sin aparecer, alerta (mail/Telegram) y NO bucla.

#### B) Systemd timer para el health-check

En `/etc/systemd/system/startips-watchdog.{service,timer}`:

```ini
# startips-watchdog.service
[Unit]
Description=Watchdog for DirectAdmin virtual IPs
After=startips.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/check-virtual-ips.sh
StandardOutput=journal
StandardError=journal
```

```ini
# startips-watchdog.timer
[Unit]
Description=Run virtual IP watchdog every 5 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s

[Install]
WantedBy=timers.target
```

Frecuencia 5 min = compromiso entre detección rápida y carga mínima. Ajustable per-server.

#### C) Alertas

Para no llenar el inbox, política de "alerta solo si auto-fix falla":

- Si auto-restart resuelve → log en journald, sin alerta.
- Si auto-restart falla 2 ciclos consecutivos → mail al operador + Telegram (vía bot existente).
- Métrica opcional: contador en `/var/log/startips-watchdog.metrics` con histórico de detecciones.

#### D) (Opcional) Métricas a Zabbix

Si Castris tiene Zabbix, exponer:

- Item `startips.virtual_ips.expected` (entero, lectura de `ip.list`).
- Item `startips.virtual_ips.active` (entero, IPs activas en interfaz que están en `ip.list`).
- Trigger: si `expected != active` durante 2 ciclos → warning.

## 3. Plan de trabajo (otra sesión)

### Fase 1 — POC manual (1-2 horas)

1. Escribir `check-virtual-ips.sh` con dry-run + apply.
2. Probar en cloud500 (caso conocido). Forzar caída con `ip addr del 87.98.230.68 dev ens3` y verificar detección + auto-fix.
3. Validar logs en journald.

### Fase 2 — Empaquetado (1 hora)

1. Mover script a `~/utilidades/directadmin/`.
2. Crear systemd unit + timer.
3. Documentar en `~/utilidades/directadmin/README.md`.
4. Empaquetar en script de instalación que copie unit + binarios + habilite el timer.

### Fase 3 — Despliegue flota DA Castris (30 min)

1. Auditar qué servidores DA tienen IPs adicionales:

   ```bash
   for s in $(sshctx list --panel directadmin --format raw); do
     n=$(ssh $s 'wc -l < /usr/local/directadmin/data/admin/ip.list')
     echo "$s: $n IPs"
   done
   ```

2. Desplegar el watchdog en los que tienen `>1 IP`.
3. Servidores conocidos con IPs adicionales: cloud500 (Burcode, 4 IPs). Resto pendiente auditoría.

### Fase 4 — Despliegue clientes externos (variable)

Servidores semi-managed (Burcode, otros): pedir autorización antes de instalar.

## 4. Casos de uso

- **Burcode cloud500** (caso semilla del incidente).
- **Flota DA Castris** que tenga IPs virtuales: srv120, srv121, kvm456, dar, amazzal, titrit, bitatrader (auditar).
- **Cualquier DA futuro provisionado con IPs adicionales** (regla preventiva: instalar el watchdog en provisión, junto con CSF/DA).

## 5. Decisiones pendientes (para sesión de implementación)

### 5.1 Frecuencia del timer

Opciones:

- **5 min**: detección rápida, carga insignificante. Recomendado.
- **1 min**: detección casi inmediata pero ruido en journald.
- **10-15 min**: caída detectable pero ventana de fallo amplia.

### 5.2 Política de retry

Opciones:

- **Auto-restart 1 vez + alerta si falla**: simple, evita bucles.
- **Backoff exponencial**: 1 min → 5 min → 15 min → alerta.
- **Solo alerta, sin auto-restart**: dejar decisión al operador.

### 5.3 Alcance del watchdog

Opciones:

- **Solo IPs virtuales DA** (lo que propone este plan).
- **Extender a otros servicios cuya caída sea silenciosa** (ej. `dataskq` cron, `assp`, etc.). Probable scope-creep — empezar simple.

### 5.4 Forma de alerta

Opciones:

- **Mail al operador** (existing).
- **Telegram via bot existente** (más rápido).
- **Ambos** (recomendado para incidentes recurrentes).

## 6. Riesgos y mitigaciones

| Riesgo | Mitigación |
|---|---|
| El watchdog se mete en bucle restartando startips si éste falla siempre | Implementar contador de retries, parar tras N fallos, alertar |
| Race condition con un cambio legítimo de IP del operador | El watchdog debe leer `ip.list` cada vez (no cachear); si se cambia `ip.list` está reflejado |
| El propio `startips.service` es buggy en DA y no funciona en algunos escenarios | Documentar exit code de startips; si falla repetidamente, alertar para fix manual |
| Ruido en journald | Log conciso (1 línea por chequeo OK, n líneas por incidente) |

## 7. Anexos

### 7.1 Script de detección manual (referencia rápida)

```bash
#!/bin/bash
# /usr/local/sbin/check-virtual-ips.sh — POC v0
set -euo pipefail

IP_LIST="/usr/local/directadmin/data/admin/ip.list"
IFACE="$(ip -4 route show default | awk '{print $5; exit}')"
MISSING=()

while read -r ip; do
  [ -z "$ip" ] && continue
  if ! ip -4 addr show "$IFACE" | grep -qE "inet $ip(/| )"; then
    MISSING+=("$ip")
  fi
done < "$IP_LIST"

if [ "${#MISSING[@]}" -eq 0 ]; then
  exit 0  # OK
fi

logger -t startips-watchdog "IPs virtuales caídas: ${MISSING[*]}"
systemctl start startips.service

# verificar tras 2s
sleep 2
STILL_DOWN=()
for ip in "${MISSING[@]}"; do
  if ! ip -4 addr show "$IFACE" | grep -qE "inet $ip(/| )"; then
    STILL_DOWN+=("$ip")
  fi
done

if [ "${#STILL_DOWN[@]}" -eq 0 ]; then
  logger -t startips-watchdog "Auto-restart OK, IPs restauradas: ${MISSING[*]}"
  exit 0
fi

logger -t startips-watchdog "ALERTA: IPs siguen caídas tras restart: ${STILL_DOWN[*]}"
# TODO: enviar mail/Telegram
exit 1
```

### 7.2 Comandos de auditoría de la flota

```bash
# ¿Qué servidores DA tienen IPs adicionales?
for s in cloud500 srv120 srv121 kvm456 dar amazzal titrit bitatrader fail500; do
  n=$(ssh $s 'wc -l < /usr/local/directadmin/data/admin/ip.list 2>/dev/null')
  echo "$s: $n IPs"
done

# ¿Y tras un test de stress, qué dominios usan IPs no-primarias?
ssh <server> '
PRIMARY=$(ip route get 8.8.8.8 | awk "/src/ {print \$NF}")
for f in /usr/local/directadmin/data/users/*/domains/*.conf; do
  ip=$(grep "^ip=" "$f" | cut -d= -f2)
  if [ "$ip" != "$PRIMARY" ] && [ -n "$ip" ]; then
    dom=$(basename "$f" .conf)
    echo "  $ip → $dom"
  fi
done | sort | uniq -c | sort -rn
'
```

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# Configuración segura de correo en DirectAdmin

Esta guía explica cómo configurar un programa de correo (Outlook, Apple Mail, Thunderbird) para acceder a una cuenta de correo alojada en nuestros servidores DirectAdmin.

**Solo se documentan conexiones seguras (cifradas).** Las conexiones sin cifrado (puertos 143, 110, 25) están desactivadas.

---

## Datos de conexión

Antes de abrir el programa de correo, necesitas tener a mano:

| Dato | Valor |
|------|-------|
| **Servidor de correo entrante** | `mail.tudominio.com` |
| **Servidor de correo saliente (SMTP)** | `mail.tudominio.com` |
| **Usuario** | Tu dirección de correo completa (`usuario@tudominio.com`) |
| **Contraseña** | La contraseña de tu cuenta de correo |

> Sustituye `tudominio.com` por tu dominio real. Si no sabes cuál es el servidor, consúltanos.

---

## Puertos y cifrado

| Protocolo | Para qué sirve | Puerto | Cifrado |
|-----------|---------------|--------|---------|
| **IMAP** | Correo entrante (sincronizado) | **993** | SSL/TLS |
| **POP3** | Correo entrante (descargado) | **995** | SSL/TLS |
| **SMTP** | Correo saliente | **465** | SSL/TLS |
| **SMTP (alternativo)** | Correo saliente | **587** | STARTTLS |

¿No sabes si usar IMAP o POP3? Consulta la guía [IMAP vs POP3: cuál elegir](#).

---

## Microsoft Outlook

### Outlook 365 / Outlook 2021 / Outlook para Microsoft 365

1. Abre Outlook. Ve a **Archivo → Agregar cuenta**.
2. Escribe tu dirección de correo y pulsa **Opciones avanzadas → Quiero configurar manualmente mi cuenta**.
3. Pulsa **Conectar**.
4. Elige **IMAP** (o POP3 si es lo que necesitas).
5. Rellena los datos del **servidor de entrada**:
   - Servidor: `mail.tudominio.com`
   - Puerto: `993`
   - Método de cifrado: **SSL/TLS**
6. Rellena los datos del **servidor de salida (SMTP)**:
   - Servidor: `mail.tudominio.com`
   - Puerto: `465`
   - Método de cifrado: **SSL/TLS**
7. Pulsa **Conectar** e introduce tu contraseña cuando se solicite.

> **Nota:** Si Outlook no acepta el puerto 465, prueba con el 587 y cifrado **STARTTLS**.

### Outlook 2016 / 2019

1. Ve a **Archivo → Configuración de la cuenta → Configuración de la cuenta**.
2. Pulsa **Nuevo** → **Configuración manual o tipos de servidores adicionales** → **Siguiente**.
3. Elige **POP o IMAP** → **Siguiente**.
4. Rellena:
   - Nombre para mostrar: tu nombre
   - Dirección de correo electrónico: `usuario@tudominio.com`
   - Tipo de cuenta: **IMAP**
   - Servidor de correo entrante: `mail.tudominio.com`
   - Servidor de correo saliente: `mail.tudominio.com`
5. Pulsa **Más configuraciones → pestaña Servidor de salida**:
   - Activa: **Mi servidor de salida (SMTP) requiere autenticación**
   - Selecciona: **Usar la misma configuración que mi servidor de correo entrante**
6. Ve a la pestaña **Avanzados**:
   - Correo entrante (IMAP): Puerto `993`, cifrado **SSL**
   - Correo saliente (SMTP): Puerto `465`, cifrado **SSL** (o `587` con **TLS**)
7. Pulsa **Aceptar → Siguiente → Finalizar**.

---

## Apple Mail (macOS)

1. Abre Mail. Ve a **Mail → Agregar cuenta**.
2. Elige **Otra cuenta de correo** → **Continuar**.
3. Escribe tu nombre, dirección de correo y contraseña → **Iniciar sesión**.
4. Si Mail no configura automáticamente los servidores, rellena:
   - Tipo de cuenta: **IMAP**
   - Servidor de correo entrante: `mail.tudominio.com`
   - Servidor de correo saliente: `mail.tudominio.com`
5. Pulsa **Iniciar sesión**.
6. Tras añadir la cuenta, ve a **Mail → Preferencias → Cuentas → tu cuenta → Avanzado** y verifica:
   - Puerto: `993`
   - Usar SSL: **activado**
7. Para el servidor de salida, ve a **Servidor de salida de correo (SMTP)** → **Editar lista de servidores SMTP**:
   - Puerto: `465`
   - Usar SSL: **activado**

---

## Mozilla Thunderbird

1. Ve a **Herramientas → Configuración de cuenta → Acciones de cuenta → Añadir cuenta de correo**.
2. Escribe tu nombre, dirección y contraseña → **Configurar manualmente**.
3. Rellena los datos del **servidor entrante (IMAP)**:
   - Protocolo: **IMAP**
   - Servidor: `mail.tudominio.com`
   - Puerto: `993`
   - Seguridad de la conexión: **SSL/TLS**
   - Método de autenticación: **Contraseña normal**
4. Rellena los datos del **servidor saliente (SMTP)**:
   - Servidor: `mail.tudominio.com`
   - Puerto: `465`
   - Seguridad de la conexión: **SSL/TLS**
   - Método de autenticación: **Contraseña normal**
5. Pulsa **Hecho**.

---

## Solución de problemas habituales

**"El servidor no responde" o "No se puede conectar"**

- Verifica que el nombre del servidor sea exactamente `mail.tudominio.com` (sin espacios, sin http://).
- Comprueba que el puerto y el tipo de cifrado coinciden exactamente con los de esta guía.
- Si usas VPN o red corporativa, desactívala temporalmente para descartar bloqueos.

**"Contraseña incorrecta"**

- Asegúrate de usar la contraseña de la cuenta de correo (no la del panel web).
- Prueba a iniciar sesión en el webmail (https://webmail.tudominio.com) con las mismas credenciales para confirmar que la contraseña es correcta.
- Si el webmail tampoco acepta la contraseña, solicita un restablecimiento.

**"El certificado no es de confianza" o advertencia de seguridad**

- Puede aparecer si el programa usa el nombre del servidor físico (ej: `srv110.example.net`) en lugar de `mail.tudominio.com`.
- Verifica que el servidor de correo configurado es exactamente `mail.tudominio.com`.
- Si el aviso persiste, contáctanos para revisar el certificado SSL.

**Correo saliente no funciona (no puedo enviar)**

- Asegúrate de que el SMTP requiere autenticación y que el usuario/contraseña son los mismos que en el correo entrante.
- Prueba con el puerto alternativo: si usas `465`, cambia a `587` con STARTTLS (o al revés).

##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).

# IMAP vs POP3: cuál elegir para tu correo

sta guía explica la diferencia entre IMAP y POP3 para que puedas elegir la opción que mejor encaja con tu forma de trabajar. Si ya tienes claro lo que necesitas, consulta la guía de configuración: [Configuración segura de correo en DirectAdmin](https://wiki.castris.com/books/directadmin/page/configuracion-segura-de-correo-en-directadmin).

---

## ¿Qué es IMAP?

IMAP (Internet Message Access Protocol) mantiene todos tus correos **en el servidor**. Tu programa de correo (Outlook, Apple Mail, etc.) los muestra como si los tuviera descargados, pero la copia original permanece en el servidor.

**Lo que eso implica:**

- Si abres el correo en el móvil, en el portátil y en el ordenador de sobremesa, ves **lo mismo en todos los dispositivos**.
- Si borras un correo en un dispositivo, desaparece en todos los demás.
- Si cambias de ordenador, no pierdes el historial de correo.
- El correo ocupa espacio en el servidor.

---

## ¿Qué es POP3?

POP3 (Post Office Protocol 3) **descarga** los correos al ordenador y, por defecto, los elimina del servidor una vez descargados.

**Lo que eso implica:**

- El correo queda almacenado localmente, en tu ordenador. El servidor se "vacía".
- Si accedes desde otro dispositivo (móvil, portátil), no verás los correos ya descargados.
- Si el ordenador falla o lo formateas sin hacer copia de seguridad, pierdes todo el historial.
- El correo **no** ocupa espacio en el servidor (una vez descargado).

> **Importante:** Algunos programas permiten configurar POP3 para dejar una copia en el servidor durante N días antes de eliminarla. Esto puede ser útil si necesitas acceso temporal desde otro dispositivo, pero no resuelve el problema de sincronización.

---

## Comparativa rápida

| | **IMAP** | **POP3** |
|---|---|---|
| ¿Los correos se guardan en el servidor? | Sí, siempre | No (se eliminan al descargar) |
| ¿Funciona bien con varios dispositivos? | Sí | No |
| ¿Ocupa espacio en el servidor? | Sí | No (tras descarga) |
| ¿Si pierdo el ordenador, pierdo el correo? | No | Sí (si no hay copia) |
| ¿Recomendado para uso profesional? | Sí | Solo en casos concretos |

---

## ¿Quiero que los correos desaparezcan del servidor?

Es una situación muy frecuente: la bandeja de entrada del servidor acumula correo y empieza a ocupar espacio. Tienes dos opciones:

### Opción A: seguir con IMAP y gestionar la limpieza (recomendada)

Esta opción no requiere cambiar la configuración. La gestión del espacio se hace desde el propio programa de correo, que tiene herramientas para ello.

**En Microsoft Outlook:**

- Crea una regla de limpieza automática: ve a **Herramientas → Limpiar carpeta** o usa la herramienta **Limpieza de buzón** (Archivo → Herramientas → Limpiar buzón de correo).
- También puedes mover manualmente los correos antiguos a una carpeta de archivo local (en tu ordenador) mediante la opción **Archivar** o **Autoarchivar**. Esos correos se guardan en un fichero `.pst` en tu equipo y desaparecen del servidor.
- Para activar el autoarchivado: clic derecho sobre la carpeta → **Propiedades → pestaña Autoarchivar** → configura el período.

**En Apple Mail:**

- Ve a **Buzones → Archivar**. Los correos archivados se mueven a una carpeta de archivo que puede ser local o seguir en el servidor, según la configuración.
- Para archivar localmente (fuera del servidor): crea un buzón local en **Buzones → Nuevo buzón → En "En Mi Mac"** y arrastra los correos allí.

**Ventaja frente a POP3:** conservas el historial completo en tu ordenador, puedes seguir buscando en correos antiguos, y el servidor queda limpio.

### Opción B: configurar POP3

Si prefieres que los correos se eliminen automáticamente del servidor en el momento de descargarlos, puedes cambiar la configuración a POP3.

Consulta la guía completa de configuración: [Configuración segura de correo en DirectAdmin](#).

Los datos que necesitarás para POP3:

| Parámetro | Valor |
|-----------|-------|
| Servidor entrante (POP3) | `mail.tudominio.com` |
| Puerto | `995` |
| Cifrado | SSL/TLS |
| Eliminar del servidor | **Sí** (o tras X días, según prefieras) |

**Ten en cuenta antes de cambiar a POP3:**

- Si accedes al correo desde varios dispositivos (móvil, ordenador del trabajo, ordenador de casa), dejarás de verlo sincronizado. Solo lo verá el dispositivo que lo descargue primero.
- Outlook puede configurarse para dejar una copia en el servidor durante un período (ej: 14 días). Eso permite cierta sincronización temporal, pero no es equiparable a IMAP.
- El historial antiguo que ya tienes en el servidor (si tienes IMAP ahora) **no desaparecerá automáticamente** al cambiar a POP3; solo se eliminarán los nuevos correos al descargarlos.

---

## ¿Cuál uso yo ahora?

Si no estás seguro de cómo tienes configurado el correo en este momento, puedes comprobarlo en Outlook en:

**Archivo → Configuración de la cuenta → Configuración de la cuenta → pestaña Correo electrónico**

En la columna **Tipo** verás si es IMAP o POP.

---

## Preguntas frecuentes

**¿Puedo tener las dos cuentas a la vez, una IMAP y otra POP3, para el mismo correo?**

No. Una cuenta de correo se configura con un protocolo u otro, no con ambos a la vez. Si quieres cambiar de IMAP a POP3, tendrás que eliminar la cuenta actual de Outlook y volver a añadirla como POP3.

**Si cambio de IMAP a POP3, ¿pierdo los correos que ya tengo en Outlook?**

Los correos que ya tienes descargados en Outlook (y que aparecen en las carpetas de tu programa) no se borran al eliminar la cuenta. Sin embargo, antes de hacer cualquier cambio, es recomendable exportar una copia de seguridad: **Archivo → Abrir y exportar → Importar o exportar → Exportar a un archivo → Archivo de datos de Outlook (.pst)**.

**¿Por qué se recomienda IMAP para uso profesional?**

Porque si el ordenador falla, toda la información de correo sigue en el servidor y se recupera simplemente configurando de nuevo la cuenta. Con POP3, si no hay copia de seguridad del fichero `.pst`, la pérdida es total.

**Ocupa mucho espacio el correo en el servidor. ¿Qué hago?**

Primero, comprueba si hay correos muy grandes (adjuntos pesados) en la bandeja de enviados o en la papelera. Son las dos carpetas que más espacio acumulan y que la gente suele olvidar limpiar. En Outlook puedes ordenar por tamaño con **Vista → Organizar por → Tamaño**.

Si tras limpiar el espacio sigue siendo insuficiente, consúltanos para ampliar la cuota del buzón.




##### Aviso
Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. 
También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad.
El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de [Castris](https://castris.com)

Si necesitas soporte profesional puedes contratar con Castris [soporte profesional](https://intranet.castris.com/store/soporte-profesional).