Naar hoofdinhoud
Contact
Lodewijk.FRL kennisbank

DirectAdmin-server installeren en inrichten: de complete handleiding

Van een lege virtuele machine tot een DirectAdmin-server die sites en mail veilig draait: besturingssysteem, DirectAdmin, nginx met HTTP/3, PHP, MariaDB, mail met spamfilter, firewall, DNSSEC, DANE, MTA-STS, back-ups en onderhoud. Alles op één pagina, in de volgorde waarin je het doet.

Naast ons werk met warmtepompen, airco’s en elektra beheren wij onze eigen servers: Proxmox op eigen hardware in het datacenter, met daarop meerdere DirectAdmin-servers. Deze handleiding is ons eigen herstelboek, algemeen gemaakt. Elke stap draait bij ons in productie.

Vul hieronder je eigen gegevens in. Alle commando’s op de pagina passen zich dan aan, en de kopieerknop neemt jouw waarden mee. Je gegevens blijven in je eigen browser; ze worden nergens naartoe gestuurd. Gebruik de informatie gerust, maar op eigen verantwoordelijkheid.

bijgewerkt 24-10-2026 Theo Lodewijk DirectAdmin 1.713 · CentOS Stream 9 een dag werk

Je eigen gegevens

blijft in je browser

Begin hier. Wat je invult, verschijnt meteen in elk codeblok; de waarden zijn gestippeld onderstreept. Plak nooit een blok waar nog een voorbeeldwaarde in staat: de voorbeeld-IP-adressen bestaan niet, en een netwerk- of nginx-blok met zo'n adres haalt je server van het net.

bijvoorbeeld mail.jouwdomein.nl
het domein van het bedrijf
hier komt de post van root en de firewall
in DirectAdmin; liever niet "admin"
24 = 255.255.255.0, 28 = 255.255.255.240
alleen als je er een hebt
niet 22, dat scheelt veel inlogpogingen
landcodes, met komma
je NAS of opslagdienst

Wachtwoorden en licentiesleutels vul je nergens op deze pagina in. Waar er een nodig is, staat <GEHEIM: ...>; die typ je pas op de server zelf.

Op deze pagina

Wat je bouwt

Eén virtuele machine met daarop alles wat een webhoster nodig heeft. Dit is de opzet die bij ons op drie servers draait:

VirtualisatieProxmox VE 8/9
BesturingssysteemCentOS Stream 9
PaneelDirectAdmin 1.7x
Webservernginx 1.31, HTTP/2 + HTTP/3
PHP8.5 (php-fpm)
DatabaseMariaDB 12.3
MailExim 4.100, Dovecot 2.4
SpamfilterRspamd 4.2 + Exim-laag
FirewallCSF + LFD
VirusscanClamAV (clamd)
DNSBIND, DNSSEC, DANE
Back-upFTPS naar NAS

Wat je vooraf regelt: een DirectAdmin-licentie, één of meer vaste IP-adressen, reverse DNS (PTR) van je IP naar de hostnaam (bij je hoster), en bij je registrar de naamservers met hun glue-records. Reken op een dag werk, waarvan een paar uur wachten op het bouwen van de software.

CentOS Stream 9 wordt ondersteund tot 31 mei 2027. Wie nu nieuw begint, kan ook voor een EL10-systeem (AlmaLinux 10 of CentOS Stream 10) kiezen: Rspamd heeft sinds kort een eigen pakketbron voor 10, en DirectAdmin ondersteunt het met een gewone (niet-lifetime) licentie. Een directe upgrade van 9 naar 10 bestaat bij CentOS Stream niet; dat is een nieuwe server en verhuizen met de back-ups uit hoofdstuk 15.

De virtuele machine (Proxmox)

Haal het ISO-bestand x86_64 van centos.org (de dvd-versie heeft alles aan boord; aarch64 is voor ARM-processors). Maak de machine op de Proxmox-host aan. Geef de schijf discard=on: wat in de machine vrijkomt, gaat dan terug naar de host.

cd /var/lib/vz/template/iso/
wget https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/iso/CentOS-Stream-9-latest-x86_64-dvd1.iso

qm create 101 --name mail.lodewijkict.nl --ostype l26 --sockets 1 --cores 8 --cpu x86-64-v2-AES \
  --memory 16384 --scsihw virtio-scsi-single --scsi0 local:200,format=qcow2,discard=on,iothread=1 \
  --net0 virtio,bridge=vmbr0 --ide2 local:iso/CentOS-Stream-9-latest-x86_64-dvd1.iso,media=cdrom \
  --boot order='scsi0;ide2;net0' --agent 1 --serial0 socket --onboot 1
qm start 101

Na de installatie de ISO eruit en de gastagent erin, dan kan Proxmox de machine netjes afsluiten en vrije ruimte terugwinnen.

qm set 101 --ide2 none,media=cdrom
# in de machine zelf:
dnf -y install qemu-guest-agent && systemctl enable --now qemu-guest-agent fstrim.timer

Maak een back-up van de hele machine voordat je iets groots doet (een update van Proxmox, een nieuwe schijf). Terugzetten kost dan minuten in plaats van een dag.

vzdump 101 --storage <back-upopslag> --mode snapshot --compress zstd

Richt ook de IPMI-kaart van de Proxmox-host in, met een tekstconsole via SSH. Dan kom je er nog bij als de host zelf niet opstart: Supermicro IPMI en noodconsole.

Het besturingssysteem inrichten

Installeer CentOS Stream 9 als "Minimal Install", schijf automatisch (LVM met xfs), tijdzone Europe/Amsterdam. Daarna als root:

Hostnaam en netwerk

hostnamectl set-hostname mail.lodewijkict.nl
echo "203.0.113.10  mail.lodewijkict.nl mail" >> /etc/hosts

nmcli con show
nmcli con mod "System ens18" ipv4.method manual \
  ipv4.addresses "203.0.113.10/24" ipv4.gateway 203.0.113.1 \
  ipv4.dns "1.1.1.1 9.9.9.9" ipv6.method ignore connection.autoconnect yes
# alleen met een tweede IP-adres: haal het hekje weg
# nmcli con mod "System ens18" +ipv4.addresses "198.51.100.10/24"
nmcli con up "System ens18"
ip -br addr; ip route; ping -c2 1.1.1.1

Heet de verbinding anders dan "System ens18", gebruik dan de naam uit nmcli con show. De oude ifcfg-bestanden in /etc/sysconfig/network-scripts gebruikt EL9 niet meer.

Tijd, pakketten en basisgereedschap

timedatectl set-timezone Europe/Amsterdam
systemctl enable --now chronyd
dnf -y install epel-release
dnf config-manager --set-enabled crb
dnf -y update
dnf -y install nano wget curl tar zstd git jq bind-utils mtr htop iftop tmux smartmontools rsync lsof

SELinux en firewalld uit

DirectAdmin werkt niet met SELinux, en de firewall regelt straks CSF.

grubby --update-kernel ALL --args selinux=0
sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
systemctl disable --now firewalld; systemctl mask firewalld

Schijfquota op xfs

Zonder quota kan DirectAdmin de schijfruimte per gebruiker niet begrenzen. Op een xfs-root moet dat via de kernelregel.

grubby --update-kernel ALL --args "rootflags=uquota,pquota"
sed -i 's#\(/dev/mapper/[a-z]*-root[[:space:]]\+/[[:space:]]\+xfs[[:space:]]\+\)defaults#\1defaults,uquota,pquota#' /etc/fstab
reboot
# na de herstart:
grep -o 'rootflags=[^ ]*' /proc/cmdline
xfs_quota -x -c state / | head -4

Logboeken comprimeren en geheugen voor Redis

Oude logboeken gecomprimeerd bewaren scheelt veel schijfruimte. De tweede regel zet een kernelinstelling die Redis (gebruikt door Rspamd) nodig heeft om zijn database veilig weg te schrijven; DirectAdmin zet hem zelf ook.

grep -q '^compress' /etc/logrotate.conf || echo compress >> /etc/logrotate.conf
echo 'vm.overcommit_memory=1' > /etc/sysctl.d/99-directadmin.conf && sysctl --system >/dev/null

SSH op een eigen poort

Een andere poort dan 22 houdt het gros van de inlogrobots weg. Straks laat de firewall die poort alleen open voor je eigen landen en adressen. Zet je eigen sleutel in /root/.ssh/authorized_keys voordat je iets dichtzet: met prohibit-password mag root alleen nog met een sleutel inloggen.

sed -i 's/^#\?Port .*/Port 1022/' /etc/ssh/sshd_config
grep -q '^Port' /etc/ssh/sshd_config || echo 'Port 1022' >> /etc/ssh/sshd_config
echo 'PermitRootLogin prohibit-password' > /etc/ssh/sshd_config.d/01-permitrootlogin.conf
echo 'UseDNS no' >> /etc/ssh/sshd_config
echo 'ClientAliveInterval 60' >> /etc/ssh/sshd_config
sshd -t && systemctl restart sshd
sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|pubkeyauthentication)'

Houd tijdens het wijzigen een tweede SSH-sessie open. Lukt het inloggen op de nieuwe poort niet, dan kun je nog terug.

DirectAdmin installeren

Het installatiescript van DirectAdmin neemt instellingen over uit omgevingsvariabelen. Zo staan hostnaam, naamservers en de naam van de beheerder meteen goed, en hoef je achteraf niets om te bouwen. De licentiesleutel staat in het klantportaal van DirectAdmin.

cd /root
wget -O setup.sh https://download.directadmin.com/setup.sh
chmod 755 setup.sh

export DA_CHANNEL=current
export DA_HOSTNAME=mail.lodewijkict.nl
export DA_NS1=ns1.lodewijkict.nl
export DA_NS2=ns2.lodewijkict.nl
export DA_EMAIL=info@lodewijkict.nl
export DA_ADMIN_USER=webhosting
export DA_ETH_DEV=ens18
export DA_FOREGROUND_CUSTOMBUILD=yes
export php1_release=8.5 php1_mode=php-fpm php2_release=no webserver=nginx mysql_inst=mariadb mariadb=12.3

bash setup.sh '<GEHEIM: licentiesleutel>'

cat /usr/local/directadmin/conf/setup.txt

In setup.txt staat het wachtwoord van de beheerder. Log in op poort 2222 van de server (https://hostnaam:2222), zet een eigen wachtwoord en haal het bestand weg. Een account met de naam "admin" is het eerste waar inbrekers naar zoeken; met DA_ADMIN_USER krijgt de beheerder meteen een andere naam.

rm -f /usr/local/directadmin/conf/setup.txt

Sinds DirectAdmin 1.6x gebruik je da build in plaats van ./build in de map custombuild, en da config-set in plaats van zelf in directadmin.conf te schrijven.

CustomBuild: welke software

CustomBuild bouwt nginx, PHP, MariaDB, Exim en de rest. Dit zijn onze keuzes. Eén PHP-versie houdt het overzichtelijk; wil je een tweede, zet dan php2_release.

da build update
da build set webserver nginx
da build set php1_release 8.5
da build set php1_mode php-fpm
for n in 2 3 4 5 6 7 8 9; do da build set php${n}_release no; done
da build set php_timezone Europe/Amsterdam
da build set php_ini_type production
da build set secure_php no
da build set php_opcache yes
da build set php_imagick yes
da build set php_redis yes
da build set mysql_inst mariadb
da build set mariadb 12.3
da build set exim yes
da build set eximconf yes
da build set blockcracking yes
da build set easy_spam_fighter yes
da build set spamd rspamd
da build set dovecot yes
da build set dovecot_conf yes
da build set pigeonhole yes
da build set clamav yes
da build set clamav_exim no
da build set phpmyadmin yes
da build set roundcube yes
da build set redis yes
da build set composer yes
da build set wpcli yes
da build set csf yes
da build set ftpd proftpd
da build set redirect_host mail.lodewijkict.nl
da build set redirect_host_https yes
da build set ssl_configuration intermediate
da build set harden_symlinks_patch yes
da build set cron yes
da build set updates yes
da build set webapps_updates yes
da build set email info@lodewijkict.nl
da build all d
da build rewrite_confs
da build versions

Een versienummer zet je niet met da build set maar in custombuild/custom_versions.txt; zie Nginx updaten. Een mail van DirectAdmin over een hoge belasting tijdens het bouwen is normaal.

Instellingen van DirectAdmin

Wat we in directadmin.conf anders zetten dan standaard, met da config-set. Daarna DirectAdmin herstarten.

# algemeen
da config-set servername mail.lodewijkict.nl
da config-set force_hostname lodewijkict.nl
da config-set ns1 ns1.lodewijkict.nl
da config-set ns2 ns2.lodewijkict.nl
da config-set ethernet_dev ens18
da config-set max_username_length 28
da config-set show_info_in_header 0
da config-set jail 1
da config-set php_fpm_max_children_default 60
da config-set php_fpm_restarts 1
da config-set check_subdomain_owner 1
da config-set logs_to_keep 5
# SSL en DNS
da config-set letsencrypt 1
da config-set acme_server_cert_enabled 1
da config-set acme_server_cert_additional_domains lodewijkict.nl,mail.lodewijkict.nl
da config-set dns_ttl 1
da config-set default_ttl 3600
da config-set dnssec 1
da config-set user_dnssec_control 1
da config-set dns_caa 1
da config-set dns_tlsa 1
# mail
da config-set dkim 1
da config-set mail_sni 1
da config-set max_per_email_send_limit 1000
da config-set user_can_set_email_limit 1
echo 1000 > /etc/virtual/limit
# inbraakbescherming (werkt samen met CSF)
da config-set bruteforce 1
da config-set brutecount 20
da config-set brute_force_log_scanner 1
da config-set hide_brute_force_notifications 1
da config-set unblock_brute_ip_time 86400
# back-ups
da config-set allow_backup_encryption 1
da config-set backup_ftp_md5 1
da config-set backup_ftp_pre_test 1
da config-set backup_gzip 2
systemctl restart directadmin

backup_gzip 2 maakt back-ups met zstd: sneller en kleiner dan gzip. backup_ftp_md5 1 zet bij elk archief een controlegetal, zodat je later kunt nagaan of de kopie op de NAS heel is.

Firewall: CSF en LFD

CSF komt mee met CustomBuild. SSH en het paneel (2222) staan niet in de gewone poortenlijst: die zijn alleen open voor de landen die je noemt en voor de adressen in csf.allow. Poort 443 op UDP is voor HTTP/3.

cp -a /etc/csf/csf.conf /root/csf.conf.origineel
CSF=/etc/csf/csf.conf
set_csf() { sed -i "s|^$1 = .*|$1 = \"$2\"|" $CSF; grep "^$1 = " $CSF; }

set_csf TESTING 0
set_csf RESTRICT_SYSLOG 3
set_csf TCP_IN  "20,21,25,53,853,80,110,143,443,465,587,993,995,35000:35999"
set_csf TCP_OUT "465,143,20,21,22,25,53,853,80,110,113,443,587,993,995,2222,1022,35000:35999"
set_csf UDP_IN  "20,21,53,853,80,443"
set_csf UDP_OUT "11335,443,20,21,53,853,113,123"
set_csf CC_SRC 2
set_csf CC_ALLOW_PORTS "NL,BE,DE"
set_csf CC_ALLOW_PORTS_TCP "1022,2222"
set_csf SYNFLOOD 1
set_csf LF_IPSET 1
set_csf LF_SSHD 5
set_csf LF_SMTPAUTH 5
set_csf LF_POP3D 10
set_csf LF_IMAPD 10
set_csf LF_DIRECTADMIN 5
set_csf LF_PERMBLOCK 1
set_csf LF_NETBLOCK 1
set_csf PT_LIMIT 0
set_csf PT_USERMEM 512
set_csf PT_USERTIME 1800
set_csf LF_EMAIL_ALERT 0
set_csf LF_PERMBLOCK_ALERT 0
set_csf LF_NETBLOCK_ALERT 0
set_csf LF_ALERT_TO "info@lodewijkict.nl"

echo "192.0.2.25 # thuis of kantoor" >> /etc/csf/csf.allow
csf -r
systemctl enable --now csf lfd

Koppel daarna de inbraakbescherming van DirectAdmin aan CSF, zodat een IP dat in het paneel of op de mail blijft proberen ook in de firewall vastloopt. Daarvoor zijn de scripts van Poralix de standaard.

cd /usr/local/directadmin/scripts/custom/
for f in block_ip.sh unblock_ip.sh show_blocked_ips.sh brute_force_notice_ip.sh; do
  wget -O $f https://raw.githubusercontent.com/poralix/directadmin-bfm-csf/master/$f
done
chmod 700 block_ip.sh unblock_ip.sh show_blocked_ips.sh brute_force_notice_ip.sh
chown diradmin:diradmin block_ip.sh unblock_ip.sh show_blocked_ips.sh brute_force_notice_ip.sh
touch /root/blocked_ips.txt /root/exempt_ips.txt

Met LF_EMAIL_ALERT 0 en de twee BLOCK-meldingen uit krijg je niet meer bij elke blokkade een mail; die liepen bij ons vroeger met honderden per dag binnen. Handig daarna: csf -g <ip> (waarom geblokkeerd), csf -dr <ip> (vrijgeven).

Nginx: HTTP/2, HTTP/3 en veilige koppen

Eigen instellingen horen in custombuild/custom/nginx/conf/ en in eigen sjablonen onder data/templates/custom/; die overleven elke update. De nginx.conf van een gebruiker is gegenereerd: wat je daar wijzigt, is bij de volgende herschrijving weg.

Algemene instellingen en TLS

mkdir -p /usr/local/directadmin/custombuild/custom/nginx/conf
cat > /usr/local/directadmin/custombuild/custom/nginx/conf/nginx-defaults.conf <<'EOF'
default_type  application/octet-stream;
tcp_nopush on;
tcp_nodelay on;
sendfile on;
log_format bytes '$bytes_sent $request_length';
keepalive_timeout 15;
types_hash_max_size 2048;
disable_symlinks if_not_owner from=$document_root;
server_tokens off;
client_max_body_size 1024m;
client_body_buffer_size 128k;
server_names_hash_bucket_size 128;
server_names_hash_max_size 10240;
proxy_read_timeout 1800s;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers on;
ssl_ecdh_curve X25519:secp384r1:prime256v1;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
ssl_stapling off;
ssl_stapling_verify off;
resolver 8.8.8.8 8.8.4.4 valid=300s ipv6=off;
resolver_timeout 5s;
include /etc/nginx/nginx-directoryindex.conf;
EOF
cp /usr/local/directadmin/custombuild/custom/nginx/conf/nginx-defaults.conf /etc/nginx/nginx-defaults.conf
nginx -t && systemctl reload nginx

Stapling staat uit omdat Let's Encrypt sinds 2025 geen OCSP meer levert. Een dhparam-bestand is niet meer nodig: er zitten alleen ECDHE-ciphers in.

Compressie

cat > /usr/local/directadmin/custombuild/custom/nginx/conf/nginx-gzip.conf <<'EOF'
gzip on;
gzip_static on;
gzip_vary on;
gzip_comp_level 5;
gzip_proxied any;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript text/x-js image/svg+xml application/rss+xml application/manifest+json;
EOF
cp /usr/local/directadmin/custombuild/custom/nginx/conf/nginx-gzip.conf /etc/nginx/nginx-gzip.conf

Server-brede regels en het QUIC-anker

/etc/nginx/nginx-includes.conf raakt DirectAdmin nooit aan. Hier zetten we certificaatcompressie, een rem op inlogpogingen bij WordPress, en een leeg serverblok dat per IP-adres HTTP/3 met reuseport opent. Zonder dat anker delen alle workers één UDP-socket, en dan haperde HTTP/3 bij ons in 2 van de 8 nieuwe sessies.

cat > /etc/nginx/nginx-includes.conf <<'EOF'
ssl_certificate_compression on;
map "$request_method:$uri" $wp_inlog_sleutel {
    default "";
    ~^POST:/(wp-login\.php|xmlrpc\.php)(?:/|$) "$server_name:$binary_remote_addr";
}
limit_req_zone $wp_inlog_sleutel zone=wp_inlog:10m rate=30r/m;
limit_req zone=wp_inlog burst=30 nodelay;
limit_req_status 429;
server {
    listen 203.0.113.10:443 quic reuseport;
    # listen 198.51.100.10:443 quic reuseport;   # alleen met een tweede IP-adres
    server_name quic-anker.invalid;
    ssl_certificate /usr/local/directadmin/conf/cacert.pem;
    ssl_certificate_key /usr/local/directadmin/conf/cakey.pem;
    access_log off;
    return 444;
}
EOF
nginx -t

Geen tweede IP? Haal die listen-regel weg. Elk IP dat een quic-listen heeft, hoort in het anker.

Eigen vhost-sjablonen

Kopieer de vier sjablonen naar custom/ en pas ze aan. In het https-sjabloon komen HTTP/2, HTTP/3 en de beveiligingskoppen. Verouderde koppen zoals X-XSS-Protection, Expect-CT en Expect-Staple laat je weg; browsers doen er niets meer mee.

cd /usr/local/directadmin/data/templates
mkdir -p custom
cp nginx_server.conf nginx_server_secure.conf nginx_server_sub.conf nginx_server_secure_sub.conf custom/
chown diradmin:diradmin custom/*
nano custom/nginx_server_secure.conf custom/nginx_server_secure_sub.conf
# direct onder:  listen |IP|:|PORT_443| ssl;
	listen |IP|:|PORT_443| quic;
	http2 on;
	add_header Alt-Svc 'h3=":443"; ma=86400' always;
|*if IP="203.0.113.10"|
	listen 198.51.100.10:443 ssl;
	listen 198.51.100.10:443 quic;
	|MULTI_IP|
|*endif|

# server_name aanvullen met:  |SERVER_ALIASES|

# |HSTS_HEADER| vervangen door:
	ssl_stapling off;
	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
	add_header X-Frame-Options "SAMEORIGIN" always;
	add_header X-Content-Type-Options "nosniff" always;
	add_header Referrer-Policy "strict-origin-when-cross-origin" always;
	add_header Permissions-Policy "geolocation=(self), microphone=(), camera=(), payment=(), usb=()" always;

Het |*if IP|-blok is alleen nodig als je een tweede IP-adres hebt waar dezelfde sites op moeten antwoorden. Doe hetzelfde in de http-sjablonen, met een listen-regel voor poort 80 op het tweede adres.

Toepassen zonder dat nginx eruit ligt

da build rewrite_confs herstart nginx hard, ook als de configuratie een fout bevat. Laat DirectAdmin daarom eerst alleen de bestanden schrijven, test, en herlaad pas daarna.

echo "action=rewrite&value=nginx" >> /usr/local/directadmin/data/task.queue
sleep 70
nginx -t && systemctl reload nginx
ss -uanl | awk '$4 ~ /:443$/' | wc -l
curl -sI --http2 https://mail.lodewijkict.nl | head -1

Per domein iets extra's nodig? Admin, Custom HTTPD Configurations: dat komt in domains/<domein>.cust_nginx en blijft staan. Testen: http3check.net, SSL Labs en securityheaders.com.

PHP en MariaDB

Eigen PHP-instellingen zet je in /usr/local/php85/lib/php.conf.d/ met een nummer hoger dan 50: DirectAdmin overschrijft alleen 10-directadmin.ini en 50-webapps.ini.

cat > /usr/local/php85/lib/php.conf.d/60-upload-voortgang-uit.ini <<'EOF'
; Upload-voortgang uit (aanvalsroute PHP2Shell, oktober 2026). Bijna geen site gebruikt het.
session.upload_progress.enabled = 0
EOF
cat > /usr/local/php85/lib/php.conf.d/99-opcache.ini <<'EOF'
opcache.memory_consumption=1024
opcache.interned_strings_buffer=64
opcache.max_accelerated_files=50000
EOF
systemctl restart php-fpm85
php -i | grep -E '^(session.upload_progress.enabled|opcache.enable |memory_limit|upload_max_filesize)'

MariaDB krijgt een buffer die past bij het geheugen (de helft van wat vrij is na de rest), luistert alleen lokaal en houdt binaire logboeken niet langer dan een dag of twee. Zonder die grens lopen de binlogs je schijf vol.

cat > /etc/my.cnf.d/custom-performance.cnf <<'EOF'
[mysqld]
innodb_buffer_pool_size=4G
innodb_file_per_table=1
slow_query_log=1
slow_query_log_file=/var/lib/mysql/slow.log
long_query_time=1
max_connections=500
EOF
cat > /etc/my.cnf.d/zz-local-bind.cnf <<'EOF'
[mysqld]
bind-address=127.0.0.1
EOF
cat > /etc/my.cnf.d/zzz-binlog-bewaren.cnf <<'EOF'
[mysqld]
binlog_expire_logs_seconds=172800
EOF
systemctl restart mysqld
mariadb --defaults-extra-file=<(da my-cnf) -e "SELECT VERSION(), @@innodb_buffer_pool_size/1024/1024/1024 AS pool_gb, @@binlog_expire_logs_seconds"
mariadb-tzinfo-to-sql /usr/share/zoneinfo | mariadb --defaults-extra-file=<(da my-cnf) mysql

da my-cnf geeft de inloggegevens van de databasebeheerder zonder dat je een wachtwoord hoeft te typen of in de geschiedenis achterlaat.

Mail: Exim, Dovecot en Rspamd

CustomBuild heeft Exim, Dovecot met sieve en Rspamd al neergezet. DKIM per domein en een eigen certificaat per maildomein (SNI) staan aan via de instellingen van stap 5. Wat we er zelf aan doen:

Spam weigeren met je eigen melding

Dat staat in een eigen artikel, met alle bestanden en een proefmatrix: Spam blokkeren met Rspamd en Exim. Kort: de lijsten staan in Rspamd, en Exim weigert dezelfde lijsten zelf, omdat DirectAdmin de tekst van Rspamd anders vervangt door een vaste eigen tekst.

echo 'enabled = false;' > /etc/rspamd/local.d/greylist.conf
rspamadm configtest && systemctl reload rspamd

Systeempostbussen die niet vollopen

Elke DirectAdmin-gebruiker heeft een systeempostbus, waar cronmeldingen en foutmeldingen in komen. Die liep bij ons ongemerkt vol met gigabytes. Nu bewaart die postbus niets meer, gaat spam weg en gaat de rest naar het gewone info@-adres.

U=""   # vul hier de DirectAdmin-gebruiker in
DOEL=info@lodewijkict.nl
: "${U:?vul eerst U in}"

cat > /home/$U/.dovecot.sieve <<'EOF'
# Systeempostbus: niets bewaren; doorsturen gebeurt in Exim.
discard;
EOF
cat > /home/$U/.forward <<EOF
# Exim filter
if \$h_X-Spam-Flag: contains "YES" or \$h_Subject: begins "*****SPAM*****" then
  seen finish
endif
deliver $DOEL
EOF
chown $U:$U /home/$U/.forward /home/$U/.dovecot.sieve
chmod 644 /home/$U/.forward /home/$U/.dovecot.sieve

Mail aan gebruiker@domein loopt via de alias van het domein, niet via .forward. Zet daarom ook in /etc/virtual/<domein>/aliases de regel <gebruiker>: info@<domein>, of <gebruiker>: :fail: Dit adres wordt niet gelezen. als er geen info@ is.

Post voor root

sed -i "s/^root:.*/root:\t\tinfo@lodewijkict.nl/" /etc/aliases
echo "info@lodewijkict.nl" > /root/.forward
exim -bt root

Wie las of verwijderde wat (optioneel)

Dovecot kan per postbus vastleggen wanneer een bericht gelezen, verplaatst of verwijderd wordt, zonder inhoud of onderwerp. Handig als een klant zegt dat zijn mail "zomaar weg" is. Eigen bestanden in conf.d laat DirectAdmin staan.

cat > /etc/dovecot/conf.d/98-maillog.conf <<'EOF'
mail_plugins {
  notify = yes
  mail_log = yes
}
mail_log_events = delete undelete expunge copy mailbox_delete mailbox_rename flag_change save
mail_log_fields = uid box msgid size flags from
protocol imap {
  mail_plugins {
    notify = yes
    mail_log = yes
  }
}
EOF
doveconf -n >/dev/null && doveadm reload

Dit is de schrijfwijze van Dovecot 2.4. Een doveadm reload verbreekt de open IMAP-verbindingen één keer.

DNS: SPF, DMARC, DNSSEC, DANE en MTA-STS

Sjablonen voor nieuwe domeinen

Eigen DNS-sjablonen in data/templates/custom/ gelden voor elk nieuw domein. Bestaande zones pas je per domein aan.

cd /usr/local/directadmin/data/templates/custom/
cat > dns_caa.conf <<'EOF'
|DOMAIN|.=0 issue "letsencrypt.org"
|DOMAIN|.=0 issuewild "letsencrypt.org"
|DOMAIN|.=0 iodef "caa@|DOMAIN|"
EOF
cat > dns_txt.conf <<'EOF'
_smtp._tls="v=TLSRPTv1; rua=mailto:rapporten@|DOMAIN|"
|DOMAIN|.="v=spf1 a mx ip4:|IP| -all"
_dmarc="v=DMARC1; p=quarantine; sp=quarantine; rua=mailto:rapporten@|DOMAIN|; fo=0"
EOF
chown diradmin:diradmin dns_*.conf

Begin bij een nieuw domein met p=none als je nog niet weet wie er allemaal namens het domein mailt (een boekhoudpakket, een nieuwsbriefdienst), en ga naar quarantine als de DMARC-rapporten schoon zijn. Zet in SPF elke dienst die namens je mailt, met include:.

DNSSEC

cd /usr/local/directadmin/scripts
./dnssec.sh keygen lodewijkict.nl
./dnssec.sh sign lodewijkict.nl
cat /var/named/dsset-lodewijkict.nl.
dig +dnssec SOA lodewijkict.nl @8.8.8.8 | grep flags

De DS-gegevens uit dsset- voer je in bij je registrar. Staat er ad tussen de vlaggen, dan klopt de keten. De sleutels staan in /var/named/ (*.key en *.private): neem ze mee in je systeemback-up. Raak je ze kwijt, haal dan eerst de DS weg bij de registrar voordat je opnieuw tekent, anders is het domein onbereikbaar voor iedereen die DNSSEC controleert.

DANE: TLSA voor de mailserver

Met DANE zegt je DNS welk certificaat je mailserver gebruikt; servers die dat controleren, sturen dan alleen versleuteld. Wij zetten TLSA 2 1 1 op de zes tussencertificaten van Let's Encrypt. Dat blijft kloppen bij elke vernieuwing, en ook als Let's Encrypt van tussencertificaat wisselt. Alleen zinvol met DNSSEC.

for h in 2e8307068b6db620e4a39d068b5dee5d6ef5788cbb2c0b6d23ead84fcc17178c \
         51aaa87d984b559ac69e929f888a022d832e089ff4dba0a412b5101bca4bc799 \
         6ebcefb4210b088654a38b03fea3d7d1c711b4fb1ddc363a45f9b1a4e53da01e \
         9d637b3d27a9e570d07607b9ccadb80a70915c7af72afce12841b1b1da825fd1 \
         a698a20824be04e47a1a33c4fa488731be92011f23a31e900e2ca26c9c2acfce \
         b3fb5d00e994cddf2cc9a4eea9f806bc5727e83cc0e4299bf956f2d524fe5376; do
  echo "_25._tcp.mail  3600  IN  TLSA  2 1 1 $h"
done
# Deze regels zet je in de zone van het domein (DNS Management in DirectAdmin).
dig +short TLSA _25._tcp.mail.lodewijkict.nl @ns1.lodewijkict.nl

Komt Let's Encrypt met een nieuw tussencertificaat? Zet dan eerst de nieuwe hash erbij. De hash van een tussencertificaat: openssl x509 -in ca.pem -noout -pubkey | openssl pkey -pubin -outform DER | sha256sum.

MTA-STS

MTA-STS doet hetzelfde als DANE voor mailservers die geen DNSSEC controleren, zoals die van Google en Microsoft. Je publiceert een klein beleidsbestand op https://mta-sts.jouwdomein.nl/.well-known/mta-sts.txt. De eenvoudigste weg in DirectAdmin: maak het subdomein mta-sts aan met een Let's Encrypt-certificaat en zet daar het bestand neer.

version: STSv1
mode: testing
mx: mail.lodewijkict.nl
max_age: 86400
mta-sts    3600  IN  A    203.0.113.10
_mta-sts   3600  IN  TXT  "v=STSv1; id=20261024T1"
_smtp._tls 3600  IN  TXT  "v=TLSRPTv1; rua=mailto:rapporten@lodewijkict.nl"

Begin met mode: testing en lees een paar weken de TLS-rapporten. Zijn die schoon, zet dan mode: enforce met max_age: 1209600 en verhoog het id in het TXT-record, zodat de grote mailproviders het nieuwe beleid ophalen.

Back-ups naar je NAS

Twee soorten, allebei nodig. De gebruikersback-up van DirectAdmin bevat per gebruiker de sites, mail en databases. De systeemback-up bevat de server zelf: configuratie, DNS-zones met DNSSEC-sleutels, en (met de aanvulling hieronder) een dump van elke database.

Gebruikersback-ups

Admin, Admin Backup/Transfer, Schedule. Kies alle gebruikers, FTP met FTPS, en zet versleutelen aan. Wij draaien twee keer per dag naar twee mappen (/admin_backups_0_0 en /admin_backups_0_12), zodat er altijd een versie van vannacht én van vanmiddag is.

IP           192.0.2.50   poort 21, Secure: FTPS
Gebruiker    backup
Wachtwoord   <GEHEIM: wachtwoord back-upserver>
Pad          /admin_backups_0_0      (00:00)   en   /admin_backups_0_12   (12:00)
Versleuteling  aan, <GEHEIM: versleutelwachtwoord>   (zonder dit wachtwoord krijg je niets terug)

Grote archieven braken bij ons af als de verbinding met de NAS even wegviel, en bleven dan half op de NAS staan. DirectAdmin gebruikt een eigen uploader als je die in scripts/custom/ zet. Kopieer het origineel en vervang de functie upload_file_ftps() door deze: keepalive op de verbinding, tot zes keer verder waar hij was, en pas klaar als de grootte op de NAS gelijk is aan het archief.

cp /usr/local/directadmin/scripts/ftp_upload.php /usr/local/directadmin/scripts/custom/ftp_upload.php
chmod 755 /usr/local/directadmin/scripts/custom/ftp_upload.php
nano /usr/local/directadmin/scripts/custom/ftp_upload.php
upload_file_ftps()
{
	/bin/echo "user =  \"$ftp_username:$ftp_password_esc_double_quote\"" >> $CFG
	ENDS_WITH_SLASH=`echo "$ftp_path" | grep -c '/$'`
	if [ "${ENDS_WITH_SLASH}" -eq 0 ]; then
		ftp_path=${ftp_path}/
	fi
	LW_LOKAAL=$(/usr/bin/stat -c %s "$ftp_local_file")
	LW_URL="ftp://$ftp_ip:${PORT}/$ftp_path$ftp_remote_file"
	LW_POGING=1
	LW_VERDER=""
	while :; do
		${CURL} --config ${CFG} ${SSL_ARGS} -k --silent --show-error --ftp-create-dirs --keepalive-time 30 --speed-limit 1024 --speed-time 300 ${LW_VERDER} --upload-file $ftp_local_file "$LW_URL" 2>&1
		RET=$?
		if [ "${RET}" -eq 0 ]; then
			LW_NAS=$(${CURL} --config ${CFG} ${SSL_ARGS} -k --silent --show-error -I "$LW_URL" 2>/dev/null | tr -d '\r' | awk -F': ' 'tolower($1)=="content-length"{print $2}')
			if [ "${LW_NAS}" = "${LW_LOKAAL}" ]; then
				break
			fi
			echo "Upload: op de NAS ${LW_NAS:-onbekend} bytes, hier ${LW_LOKAAL} (poging ${LW_POGING}); opnieuw"
			RET=98
			LW_VERDER=""
		else
			echo "Upload: curl ${RET} bij poging ${LW_POGING}; verder waar hij was"
			LW_VERDER="-C -"
		fi
		if [ "${LW_POGING}" -ge 6 ]; then
			echo "curl return code: $RET"
			break
		fi
		LW_POGING=$((LW_POGING+1))
		sleep 30
	done
}
bash -n /usr/local/directadmin/scripts/custom/ftp_upload.php && echo "syntax ok"

Systeemback-up, met alle databases

Admin, Admin Backup/Transfer, System Backup: zelfde NAS, eigen map (bijvoorbeeld /systeembackup). Daar stel je in wat er mee moet en waarheen; het tijdstip komt uit de cronregel hieronder, dus plan daar niet nog een tweede in. Twee dingen moesten wij repareren. De systeem-curl (7.76) kapt FTPS-uploads met TLS 1.3 aan het eind af, waardoor elk archief een paar MB te kort was; een eigen curl-instelling voor alleen deze taak lost dat op. En DirectAdmin 1.7x dumpt in de systeemback-up alleen nog de Roundcube-database; een klein script zet de rest erbij.

mkdir -p /etc/sysbk-curl /usr/local/sysbk-extra
printf 'tls-max = 1.2\n' > /etc/sysbk-curl/.curlrc

cat > /usr/local/sysbk-extra/databases.sh <<'EOF'
#!/bin/bash
# Alle databases voor de systeemback-up; sysbk.custom_paths neemt de map mee in het archief.
set -u
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DOEL=/var/lib/sysbk-extra/databases
umask 077
rm -rf "${DOEL:?}"; mkdir -p "$DOEL"
fouten=0
for db in $(mariadb --defaults-extra-file=<(da my-cnf) -N -e 'SHOW DATABASES'); do
  case "$db" in information_schema|performance_schema|sys) continue ;; esac
  mariadb-dump --defaults-extra-file=<(da my-cnf) --single-transaction --routines --events --triggers \
    --add-drop-database --databases "$db" 2>>"$DOEL/fouten.txt" | gzip > "$DOEL/$db.sql.gz"
  [ "${PIPESTATUS[0]}" -eq 0 ] || { fouten=$((fouten+1)); echo "FOUT: $db"; }
done
( cd "$DOEL" && md5sum -- *.sql.gz > md5.txt )
echo "$(date '+%F %T') $(ls "$DOEL"/*.sql.gz | wc -l) databases, fouten: $fouten"
exit $((fouten > 0))
EOF
chmod 700 /usr/local/sysbk-extra/databases.sh
echo /var/lib/sysbk-extra/databases >> /usr/local/directadmin/data/admin/sysbk.custom_paths

(crontab -l 2>/dev/null | grep -v sysbk; echo '0 5 * * * /usr/local/sysbk-extra/databases.sh >> /var/log/directadmin/sysbk.databases.log 2>&1; PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin CURL_HOME=/etc/sysbk-curl /usr/local/directadmin/shared/sysbk.sh -q; rm -rf /var/lib/sysbk-extra/databases') | crontab -
crontab -l

Een systeemback-up waarvan de upload mislukt, laat zijn map in /backup staan; die moet na een nacht leeg zijn. Het archief is .tar.zst: op Windows open je dat met 7-Zip of PeaZip, de Verkenner toont het als leeg.

Terugzetten

Eén gebruiker: Admin Backup/Transfer, Restore, From FTP, het archief kiezen en "use the IP from the backup". Zet vóór een herstel van een hele server eerst de IP-adressen terug in IP Management; dan krijgt elke gebruiker zijn eigen IP terug. Om één bestand uit een versleuteld archief te halen:

read -rsp 'Versleutelwachtwoord: ' PW; echo; printf '%s' "$PW" > /root/bk.pw; chmod 600 /root/bk.pw; unset PW
/usr/local/directadmin/scripts/decrypt_file.sh user.webhosting.<gebruiker>.tar.zst.enc /root/terug.tar.zst /root/bk.pw
rm -f /root/bk.pw
mkdir /root/terug && tar --zstd -xf /root/terug.tar.zst -C /root/terug

Controleer na de eerste nacht of elk archief een .md5 naast zich heeft en of de grootte op de NAS gelijk is aan die op de server. Een back-up die je nooit hebt teruggezet, is een hoop.

Virusscan voor uploads (ClamAV)

Draait er een CMS dat uploads laat scannen? De PHP van een site staat in DirectAdmin in een afgesloten kooi met alleen de eigen homemap en /usr. Een losse clamscan vindt daar zijn virusdatabase niet (die staat in /var) en kost bovendien ruim 14 seconden en 1,3 GB per bestand. Laat de site daarom met de draaiende clamd praten.

grep -E '^(TCPSocket|TCPAddr)' /etc/clamd.d/scan.conf     # moet 3310 en 127.0.0.1 zijn
systemctl enable --now clamav-freshclam clamd@scan
printf 'TCPSocket 3310\nTCPAddr 127.0.0.1\nStreamMaxLength 2100M\n' > /usr/local/etc/clamav-cms.conf
cat > /usr/local/bin/clamscan-cms <<'EOF'
#!/bin/sh
# clamscan-vervanger voor PHP in de kooi: praat met clamd en stuurt de inhoud mee (--stream).
CONF=/usr/local/etc/clamav-cms.conf
for arg in "$@"; do
	[ "$arg" = '--version' ] && exec /usr/bin/clamdscan --config-file="$CONF" --version
done
n=0; for arg in "$@"; do n=$((n + 1)); done
i=0
for arg in "$@"; do
	i=$((i + 1))
	[ "$arg" = '--stdout' ] || set -- "$@" "$arg"
	[ "$i" -lt "$n" ] || break
done
shift "$n"
exec /usr/bin/clamdscan --config-file="$CONF" --stream --no-summary "$@"
EOF
chmod 755 /usr/local/bin/clamscan-cms

Laat het CMS /usr/local/bin/clamscan-cms gebruiken in plaats van clamscan. De exitcodes zijn gelijk (0 schoon, 1 besmet, 2 kon niet scannen) en een scan kost milliseconden.

Onderhoud

Het wekelijkse rondje staat uitgebreid in DirectAdmin en CentOS bijwerken; nginx op een vaste versie in Nginx updaten. Verder:

# schijfruimte en wie hem gebruikt
df -h / ; du -xsh /home/* 2>/dev/null | sort -h | tail -15
# gebruikscijfers in DirectAdmin bijwerken (user.usage is een oude tussenstand)
echo "action=tally&value=all" >> /usr/local/directadmin/data/task.queue
# mailwachtrij
exim -bpc
exim -bp | exiqsumm | head -20
exiqgrep -i -f '<afzender>' | xargs -r exim -Mrm
# een postbus opschonen
doveadm search -u <adres> mailbox INBOX.spam savedbefore 90d | wc -l
doveadm expunge -u <adres> mailbox INBOX.spam savedbefore 90d > /dev/null
# firewall
csf -g <ip>; csf -dr <ip>; csf -ta <ip> 3600

Stuur de uitvoer van doveadm nooit door | head: dan stopt doveadm halverwege. En haal de mailwachtrij niet meer leeg door de spoolmap te verplaatsen; dan ben je alle wachtende mail echt kwijt.

Controlelijst: is de server klaar?

hostname -f; ip -br addr; timedatectl | grep -E 'Time zone|synchronized'
grep -o 'rootflags=[^ ]*' /proc/cmdline; getenforce; systemctl is-active firewalld
sshd -T | grep -E '^(port|permitrootlogin)'
csf -v; da build versions | grep -B1 -i installed | head -40
nginx -t; curl -sI https://mail.lodewijkict.nl | head -1
php -v | head -1; php -i | grep upload_progress.enabled
mariadb --defaults-extra-file=<(da my-cnf) -e 'SELECT VERSION()'
exim -bV | head -1; exim -bpc; doveconf -n >/dev/null && echo dovecot-ok; rspamadm configtest
grep '^root:' /etc/aliases; crontab -l
systemctl --failed --no-legend
  • Van buiten: internet.nl voor web en mail (DNSSEC, DANE, STARTTLS, IPv6), en een mail heen en terug met Gmail: in de kop dkim=pass en spf=pass.
  • HTTP/2 en HTTP/3: http3check.net. Certificaat en ciphers: SSL Labs, minimaal A.
  • Na de eerste back-upnacht: archieven met .md5 op de NAS, /backup leeg, het databaselog zonder fouten.

Valkuilen die ons tijd kostten

  • Twee servers die allebei "mail" heten. De prompt [root@mail ~]# zegt niets. Kijk vóór een wijziging altijd met hostname -f waar je bent.
  • su - gebruiker doet stil niets als SSH voor die gebruiker uitstaat (de shell is dan /bin/false). Je blijft root, zonder foutmelding. Gebruik su -s /bin/bash - gebruiker.
  • Kopieën als root tellen mee in de quota van de gebruiker. cp -a houdt de eigenaar vast; een back-upkopie in /root liep zo de quota van een klant vol. Geef zo'n kopie daarna chown -R root:root.
  • Een gebruiker wijzigen via de API zet alles terug wat je niet meestuurt. CMD_API_MODIFY_USER met customize zette bij ons php, ssl en catch-all uit. Maak eerst een kopie van user.conf en stuur de volledige set mee.
  • Een DNS-record wijzigen via de API: geef de oude waarde precies mee zoals hij in de zone staat. Dubbel gecodeerd vindt DirectAdmin hem niet, zegt toch "success" en zet het nieuwe record ernaast. Twee _dmarc-records maken DMARC ongeldig.
  • Niet-leesbare bestanden breken een strikte back-up. Sockets in een homemap (bijvoorbeeld van een Node- of spraakdienst) gaven bij ons een foutmail per nacht. da config-set add_non_readable_files_to_strict_backup 0 of sluit het pad uit.
  • Een nieuwe GPG-sleutel bij een pakketbron. Rspamd wisselde in oktober 2026 van sleutel; daarna faalde elke dnf update. rpm --import https://rspamd.com/rpm/rspamd.asc && dnf -y makecache.
  • Een map opruimen op leeftijd van de map. De wijzigingsdatum van een map zegt niets over de bestanden erin. Kijk naar de inhoud en naar de crontabs voordat je iets verplaatst.

Verder in de kennisbank

Hulp nodig met een DirectAdmin-server?

Loop je ergens vast bij het inrichten, komt legitieme mail onterecht niet aan, of wil je dat wij het voor je doen? Neem contact op.

Delen