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.
Je eigen gegevens
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.
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:
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=passenspf=pass. - HTTP/2 en HTTP/3: http3check.net. Certificaat en ciphers: SSL Labs, minimaal A.
- Na de eerste back-upnacht: archieven met
.md5op de NAS,/backupleeg, 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 methostname -fwaar je bent. su - gebruikerdoet stil niets als SSH voor die gebruiker uitstaat (de shell is dan/bin/false). Je blijft root, zonder foutmelding. Gebruiksu -s /bin/bash - gebruiker.- Kopieën als root tellen mee in de quota van de gebruiker.
cp -ahoudt de eigenaar vast; een back-upkopie in/rootliep zo de quota van een klant vol. Geef zo'n kopie daarnachown -R root:root. - Een gebruiker wijzigen via de API zet alles terug wat je niet meestuurt.
CMD_API_MODIFY_USERmetcustomizezette bij ons php, ssl en catch-all uit. Maak eerst een kopie vanuser.confen 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 0of 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
Op onderdelen van deze handleiding gaan we in losse artikelen dieper in. Alle artikelen, de veelgebruikte commando’s en onze blokkadelijsten staan in de DirectAdmin kennisbank.
Supermicro IPMI en noodconsole
Bij het scherm komen als de server niet opstart, ook met het verlopen KVM-certificaat.
Lees artikelSpam blokkeren met Rspamd en Exim
Bewezen spam weigeren met je eigen melding en contactlink.
Lees artikelDirectAdmin en CentOS bijwerken
Het maandelijkse onderhoud via SSH, en daarna controleren.
Lees artikelNginx updaten
Met de actuele versie van nginx.org, veilig herladen.
Lees artikelChecks overslaan bij mail filtering
Als legitieme mail onterecht wordt geweigerd of vertraagd.
Lees artikelHulp 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.