Version 1.61.4

Released: 2020-09-18

Script to help reset the da_admin password new

Proposed script: /usr/local/directadmin/scripts/

Usage: --stdin         - the password will be passed on stdin --random     - pick a new random password --password='newpass'                    - this help page

The script should attempt to use the mysql= value from the setup.txt if it exists, (else use current da_admin user/pass), and make an attempt to connect, taking into account the host value, if exist in the mysql.conf NOTE: it currently only support localhost resets

If it's able to connect, change the password normally via mysql call, and update the mysql.conf and my.cnf.


If it's not able to connect, attempt to shut down mysqld, and restart it with the --skip-grant-tables, as per guide: and again, reset the da_admin pass, followed by restarted mysqld back into normal mode.

After setting the password, also udpate the mysql.conf my.cnf.


Should the script be successful, a possible API call may be added to call the script through 2222. To be determined.


Per-Domain php.ini settings through GUI (TEMPLATES)(SKINS) new

Per-User control, for per-domain php.ini settings. Currently only saves the changes to the "DOMAIN CONFIG" listed below (more usage to follow shortly)

For "Enhanced" style skins, you'd view the domain's settings at: CMD_PHP_SETTINGS?


php-fpm only supporst 1 php-fpm.conf file (one per User) You'll need to set the desired php settings on the "Main Domain" of the account to alter php settings for this User. Other php modes like mod_php+mod_ruid2, or fastcgi support per-domain php settings.


GET: CMD_PHP_SETTINGS? returns JSON list with 2 sub-lists: "domain_php_ini" : containing all php.ini settings, based on the DOMAIN CONFIG below "template_php_ini" : contains the json from the template['ini'] item with info on each php.ini that can be set.

The domain_php_ini is a basic php.ini json array without sub-arrays. The template_php_ini has each index as the php.ini name, with the value being an array. Each template php ini item sub-array will have a "type" and "default" value set. See the next section for info on template types.


Should you want to disable the User's ability to use this feature, the commands.deny could be used, or use the never commands, eg:

/usr/local/directadmin/direcadmin set never_commands CMD_PHP_SETTINGS
service directadmin restart


-"bool": Can be "On" or "Off" (note the upper case first letter).

  • "list": will have a sub-array called "values" being the values which can be chosen from.
  • "value": basic input text. There are some character restrictions done on the back-end.
  • "int": will have a sub-array called "range", which is an integer array with 2 items, the low and the high values.

See the TEMPLATE below for more samples.



In a standard php.ini syntax: name=value ...


Let's say we want to save: max_execution_time=45

To save a change, use:

method: POST

Where DA only notices which values are to be saved when "save_" is prefixed to the front. For json/API calls, multiple save_max_execution_time type values can be passed in the same post. Enhanced just uses 1 at a time.


method: POST


Enhanced: /usr/local/directadmin/data/skins/enhanced/user/php_settings.html

with a link to CMD_PHP_SETTINGS? in enhanced/user/modify_domain.html to the right of the PHP on/off checkbox, called "Php Settings"


  • virtual_host2*.conf: added: |CLI_PHP_SETTINGS| within the |*if CLI="1"| section.
  • user_virtual_host.conf: added: |CLI_PHP_SETTINGS| within the |*if USER_CLI="1"| section.
  • php-fpm.conf: added |FPM_PHP_SETTINGS| before |CUSTOM2|
  • php-cron.ini: added |CRON_PHP_SETTINGS| at the bottom (php_settings.json and php-cron.ini values must not overlap)
  • openlitespeed_vhost.conf: added |CLI_PHP_SETTINGS| after |CLI_PHP_MAIL_LOG|

NEW: /usr/local/directadmin/data/templates/php_settings.json

Contents, at the time of this writing:

        "ini" : {
                "allow_url_fopen" : {
                        "default" : "On",
                        "type" : "bool"
                "display_errors" : {
                        "default" : "On",
                        "type" : "bool"
                "error_reporting" : {
                        "default" : "E_ALL & ~E_NOTICE",
                        "type" : "list",
                        "values" : \[
                                "E_ALL & ~E_NOTICE",
                "file_uploads" : {
                        "default" : "On",
                        "type" : "bool"
                "include_path" : {
                        "default" : "\\".;/path/to/php/pear\\"",
                        "type" : "value"
                "log_errors" : {
                        "default" : "Off",
                        "type" : "bool"
                "mail.force_extra_parameters" : {
                        "default" : "",
                        "type" : "value"
                "max_execution_time" : {
                        "default" : 30,
                        "type" : "int",
                        "range" : \[ 0, 14400 \]
                "max_input_time" : {
                        "default" : 60,
                        "type": "int",
                        "range" : \[ 0, 14400 \]
                "max_input_vars" : {
                        "default" : 1000,
                        "type": "int",
                        "range" : \[ 1, 100000 \]
                "memory_limit" : {
                        "default" : "128M",
                        "type" : "list",
                        "values" : \[
                "post_max_size" : {
                        "default" : "8M",
                        "type" : "list",
                        "values" : \[
                "register_globals" : {
                        "default" : "Off",
                        "type" : "bool",
                        "require" : {
                                "php_ver" : "<5.4.0"
                "session.gc_maxliftime" : {
                        "default" : 1440,
                        "type" : "int",
                        "range" : \[ 1, 1209600 \]
                "short_open_tag" : {
                        "default" : "On",
                        "type" : "bool"
                "upload_max_filesize" : {
                        "default" : "2M",
                        "type" : "list",
                        "values" : \[
                "zlib.output_compression" : {
                        "default" : "Off",
                        "type" : "bool"


Optional zstd compression for backup/restore new

DirectAdmin now supports the tar.zst format, using the zstd compression, which is far better than gzip in terms of space used, as well as compression and decompression performance.

To enable, ensure it's installed, using CustomBuild:

cd /usr/local/directadmin/custombuild
./build zstd

Then tell DA you have it, and enable it for backups:

cd /usr/local/directadmin
./directadmin set zstd 1
./directadmin set backup_gzip 2

The backup_gzip may get renamed to backup_compression at some point, as the above isn't entirely clear it's no longer using gzip.

Backup: Option to exclude Trash (SKINS) new

Both the Admin Level and User Level backups will now offer a "Deleted Trash Data" checkbox. This controls the inclusion of ~/.trash in the backup/home.tar.gz, within the main backup file.

The CMD_ADMIN_BACKUP and CMD_SITE_BACKUP will now be upgraded from: form_version=3

to: form_version=4

Where any form_version less than 4 will automatically have "select13=trash" selected, if "select0=domain" is selected. Anything from 4 an up will require select13=trash to be checked/passed if that value is desired.

There is also a new internal directadmin.conf option: skip_trash_in_backups=0 where it can be set to 1 to always exclude it for all backups, regardless checkbox.

Keep in mind that the 13 in "select13" is not important. Only that there are not any duplciate selectX numbers, as DA does not handle duplicate select[] form values, unless told to do so in the back-end (FileManager)


The user.admin.fred.tar.gz backup files will now include:

  • trash
  • trash_aware

with or without "trash", depending on the checkbox, but this version of DA an onwards will include the trash_aware option. Absence of trash WITH trash_aware will eventually exclude ~/.trash from being restored. Currently (1.61.4) the ~/.trash folder will always be restored as the home.tar.gz is simply extracted into /home/user, without any per-file choices. So for the restore (at the moment), trash will always be restored if present in the backup.


the scripts/ will call the action=convert&cronbackups, as it always does, but the code this triggers will now update all crons to enable "trash" if "domain" is enabled.


  • /usr/local/directadmin/data/skins/enhanced/user/site_backup.html
  • /usr/local/directadmin/data/skins/enhanced/admin/admin_backup.html
  • /usr/local/directadmin/data/skins/enhanced/admin/admin_backup_modify.html

Update the form_version=4 and include select13=trash. The control token is called |TRASHON| to have it check or not in the modify page.

R27277 EVO1932

Suspension notifications for manual suspension (SKINS) new

Previously, only the automated suspensions would send a notice for bandwidth over-usage. Manual suspensions would not send any notice, as it would be triggered by the creator.

This new feature will provide a method to send a notice to the Message System (thus E-Mail) as to why they've been manually suspended. Features will include:

  • a new page to allow management of the default message to use.
  • Inclusion of a |REASON| token that can be used in this message
  • Ability to use if-then-else syntax on this |REASON| to control full sentences based on it's value.
  • Default option to send this message upon manual suspension or not.

With the Evolution skin, upon suspension intent, a new modal pop-up will offer the choice of the reason (as before), but will also include the choice if the message should be sent or not. This modal will also include the full default subject and message, which can either be left as-is or altered for this one message delivery (without affecting the default).

The creator of the account will also get a duplicate. Note that if an Admin suspends the User, but that Admin did not create the account, the Reseller that did create the account will get a copy of the email.

Enhanced will rely on the default page for the message and delivery choice.


When saving a message, the following tokens are available. Note that the message is tokenized first, allowing you to set tokens which are passed to the subject (if ever needed)

USERNAME: account being suspended
USERTYPE: type of account being suspended: user|reseller|admin (useful mainly only for message overrides, see SUSPENSION below). 
CREATOR: creator of this User, even if the account doing the suspension is not the creator.
EMAIL: email address of the User, from the user.conf (may differ from the User's message system email).
DOMAIN: main domain of the User (user.conf), may be blank if they have 0 domains.
HOSTNAME: hostname of the server.
REASON: reason for the suspension passed. If no reason is passed, will be set to "none"
REASON_STR: Human text from the data/templates/suspension_reason.txt or data/templates/custom/suspension_reason.txt). May be blank if REASON is not in there.
MSG_FOOTER: recommended to leave as last text in the message. Standard footer is added.

You can use the standard if-then-else syntax on any of the tokens, and even set your own tokens if needed.


|*if REASON="spam"|
You sir, are a spammer.
You were suspended. Reason: |REASON|



View the current settings: CMD_SUSPENSION_MESSAGE?level=1

View the current message for the User.

  • level=1 User suspensions.
  • 2, 3, are for Reseller, Admins.

Add &json=yes for the subject, message, and "settings['notify'] == 'yes'|'no'"


method: POST
save=<any text>
level=1|2|3   (Resellers can only use 1)
subject=Subject text
message=message text
notify=yes|no  (checkbox, so not passed is 'no')


To undo whatever you've done for this given level, pass:
method: POST

and that level will be deleted.


The usual suspension is still the same, but extra optional values can be passed. Without adding the extra values during suspension, if "notify=yes" was set in the settings, the message will be sent. The only way to override this would be to pass "notify=no' along with the suspension request, eg:

method: POST
(reason=<suspend reason>)

OVERRIDES / optional:

subject=subject text
message=message text

where all 3 options extras are optional. You do not need to pass all 3.. can be any of them, or all of them, or 2, etc.. The subject/message texts are still tokenized normally.

The reason is optional, but we highly recommend setting it if a message is to be sent out. Without a reason, it will be set to "none", which may cause confusion.

Keep in mind, that the Admin Level -> Show All Users page can suspend all 3 types of accounts in 1 request. if the subject/message is passed along with the suspension, this will not be type-specific. In that case, you can use the |USERTYPE| token to do some if-then-else statements in the message


The files saved on disk will be at:

  • /usr/local/directadmin/data/users/creator/u_suspension.json
  • /usr/local/directadmin/data/users/creator/r_suspension.json
  • /usr/local/directadmin/data/users/creator/a_suspension.json


Unlike other email notices, we've opted to not use a default template for this tool. Instead, it makes use of the internal "gettext" .pot lang files for easier translation.



The edit_message.conf file already existed for editing the welcome messages, but it was re-used with if-then-else to accommodate the new feature. There were no changes to files_reseller.conf to point to it.


templates/custom/additional_forbidden_domains.list new

Relating to the existing forbidden_domains.list template, this new optional template can be used to add a custom supplementary list of domains, without needing to worry about adding new domains if the default template is modified.

Thus, with this change, the new recommended way to add domains to the list is to create the template: /usr/local/directadmin/data/templates/custom/additional_forbidden_domains.list

and simply include the extra domains you wish to add to this file (one domain per line)

User controlled per-domain ModSecurity flags (TEMPLATES)(SKINS) new


Feature that enables Users to skip some mod_security rules, or fully disable them when needed.

<ID> below are rule skip IDs, called SecRuleRemoveById in ModSecurity must be a positive integer, but ranges are allowed, as long as they're "quoted", eg: 1234 "1234-1239"



When called by an Admin, absence of the domain will return the global modsecurity_rules file.


Should you want to disable the User's ability to use this feature, the commands.deny could be used, or use the never commands, eg:

/usr/local/directadmin/direcadmin set never_commands CMD_MODSECURITY
service directadmin restart


JSON output only of the modsec_audit.log

REQUIRES modsec_audit.log to use the new one-json-per-line format.

Users/Admins: CMD_MODSECURITY?action=log&

Only show entries matching this domain. For Users, the domain must be in the domains.list. For Admins, can be any host value they want. Sub-domains will be included in the output. Blank hosts are not included.

Admins: CMD_MODSECURITY?action=log

Shows entries with any Host value (or no Host)

To reduce the log output, you should include: &lines=1000 to any value. DA starts from the end of the log, parsing lines backwards. It stops after this number of ENTRIES has been added to the log (was a tail, but it's now entries)

There is also an internal max_time=15 (which is dynamic, timeout / 4, assuming timeout=60) You can pass &max_time=5 or any other number of seconds, to have the parser stop after this number of seconds if you wish to speed up the display, at the cost of losing some older entries. For very large logs, there is no point in parsing the entire thing if a timeout will happen.

The logs will be output in a "logs" array, filled with a list of transaction arrays. The top-level json also includes a "summary" array, giving info on how the parser actually went eg:

"max_time": "15",
"requested_lines": "500",
"returned_lines": "375",
"time_abort": "yes"

where if you see "time_abort" ; "yes", it would mean that time ran out before actually finding that number of lines/entries.


method: POST

optional, can also include to save a call if saving and skipping in 1 request:


When called by an Admin, absence of the domain will save to the global modsecurity_rules file. Same for the rule skips below.


method: POST


method: POST




returns 2 arrays, one for On/Off flags, and the other for skip IDs, eg:

"SecRuleRemoveById" :
"SecFilterScanPOST": "On",
"SecRuleEngine": "On"

You can learn if modsecurity=yes is set in the options.conf via: CMD_ADDITIONAL_DOMAINS?action=view& with the added value: modsecurity=yes|no

When called by an Admin, absence of the domain will return the global modsecurity_rules file.

You'll also get a "subdomain_select" array, which is a standard select-box for the available subdomains.


Any page can override just one subdomain, instead of the top domain with all sub-values by including "subdomain=sub" in the GET/POST values. The config will be at: /usr/local/directadmin/data/users/USER/domains/DOMAIN.COM.subdomains_modsecurity_rules/SUB.modsecurity_rules


When saved to disk, the path will be: /usr/local/directadmin/data/users/USER/domains/DOMAIN.COM.modsecurity_rules based on the template, below.

If an Admin is making a call to CMD_MODSECURITY, they are allowed to either pass the domain of some other User, or no domain at all. The global config for mod security will be stored at: /usr/local/directadmin/data/admin/modsecurity_rules to be included by the webserver configs.



Where flags are stored into |FLAGS| and the multi-line SecRuleRemoveById values are saved into |DISABLEDRULES| There template starts with |CUSTOM1| and ends with |CUSTOM2| but these tokens are currently blank for possible future expansion.

virtual_host2*.conf: added new token:


within the <Directory> context.

nginx_server*.conf openlitespeed_vhost.conf


New file: /usr/local/directadmin/data/skins/enhanced/user/mod_security.html

Modified: /usr/local/directadmin/data/skins/enhanced/user/modify_domain.html

to include a button when:

|*if HAS_MOD_SECURITY="yes"|

pointing to:


Unrelated: also changed all class=list tables in modify_domain.html to use the cleaner <table class=list_alt> which does not require extra tags for td entries, and uses th for table titles/footers.


LetsEncrypt: Lego dnsproviders for wildcard certs on various remote dns services new

Wildcard certificates for LetsEncrypt requires DNS confirmation. If you're running at some remote DNS provider, not currently supported by the multi-server setup, then this tool lets you use wildcard certs with those DNS providers.


To get this to work, you'll need to update your script to also download the the: from your chosen download server.


cd /usr/local/directadmin/custombuild
./build update
./build letsencrypt


To get the list of dns providers, include &dnsproviders=yes in the request, eg: CMD_SSL?

as the file is somewhat large, so only load it if the given skin knows how to use it.

It will be loaded in a top-level array called:

dnsproviders["data"] = { "version" : "3.7.0", "acme-dns": ...}

and any info about the current domain settings will be in: dnsproviders["dnsprovider"] where the "dnsprovider" (singular) is a dump of the file, used to auto-fill the pre-selected choice. If this file contains "inherit=creator" or "inherit=global", it will use the respective dnsprovider.conf file.


An Admin/Creator can setup 2 possible inherit files: Global: /usr/local/directadmin/data/admin/dnsprovider.conf

Creator: /usr/local/directadmin/data/users/resellerbob/dnsprovider.conf

Should either of these exist, where creator=resellerbob is in the given User's user.conf, they'll be included in the list of dnsproviders["data"] output as:

  • dnsproviders["data"]["inherit-creator"]
  • dnsproviders["data"]["inherit-global"]

where each would still have the correct "Name" bug the type is prefixed, eg: dnsproviders["data"]["inherit-creator"]["name"] = "Inherit Creator : Cloudflare"

where the "Inherit Creator : " or "Inherit Global" would be prefixed, beside the name from the used type for that inherited dnsprovider.conf type.

The inherit-creator or inherit-global arrays will have an empty "credientials" array, and zero credentials are allowed to be passed if the master (including inherited configs) have zero creds.

If the "dnsprovider" is empty (nothing picked yet), check for: dnsproviders["settings"]["default"] to know which selection should be used by default. It should be either "local", "inherit-creator" or "inherit-global".

The Global/Creator dnsprovider.conf files may contain one of: default=inherit-creator default=inherit-global default=local

which is what specifies the default value for User to have selected.


Resellers/Admins can set an 'inherit-creator' dnsprovider.conf file. Admins can also set the 'inherit-global' dnsprovider.conf file.


Either can view that config (if exists) via: CMD_SSL?action=dnsprovider&json=yes

or: CMD_SSL?action=dnsprovider&json=yes&type=global

which provides a similar array, eg:

dnsproviders["type"] = "creator" | "global"
method: POST
default=local|inherit-global|inherit-creator   (optional)

where the last 3 depend on which "dnsprovider" was selected, similar to below for Users. The default is optional and is used to tell the User which default selection to use. Saved in creator/reseller dnsprovider.conf

If no "type" is passed, "creator" is used. A Reseller is not allowed to set type=global.

method: POST

which simply deletes the given Admin/Reselelr dnsprovider.conf file.


When saving data for a LetsEncrypt request for Users, include "dnsprovider=NAME" to activate the rest of the checks, eg:

method: POST


for example, assuming cloudflare is the desired remote dnsprovider.

Note, for inherited dnsprovider, do not pass a dnsprovider, else it will override the inherited value with the passed value.


If you wish to only save the dnsprovider info, use:

method: POST


to remove the file (resetting to Local), include: dnsprovider_reset=yes

to either of the above requests. The "dnsprovider=" nor it's related fields are needed when dnsprovider_reset=yes is passed.


method: POST


When a selection is made by a User (or by creator default choice), the domain's dns setting will be stored in: /usr/local/directadmin/data/users/USERNAME/domains/DOMAIN.COM.dnsprovider

sample data:


OR: inherit=yes

(or something similar)

which is loaded into the ENV and passed onto the script.

Hide old directadmin.conf mysql settings when mysql_detect_correct_methods=1 new

New installs have been using mysql_detect_correct_methods=1 for a while now.

When this setting is enabled, the 2 older settings:


have no effect and only cause confusion. When mysql_detect_correct_methods=1 is enabled (recommended), then those 2 older setting will be hidden from any output, eg: ./directadmin c

output main Admin username new

Many scripts require the top-level admin account name. This is not always going to be "admin" so to unifiy the value, new command: ./directadmin a

which would output nothing more than: admin

Default: disable_ip_check=1 new

Relating to the disable_ip_check feature, we was previously set to 0, such that the incoming session IP must match for subsequent requests, the internal default is now set to 1, eg: disable_ip_check=1

This is due to IPv6 and IPv4 IPs commonly rotating for each request, causing confusing logouts.


Option to disable forwarder loop check new

Relating to feature "Forwarder anti-loop check": Forwarder anti-loop check

this provides the option to disable it.

New internal default: forwarder_loop_check=1

to disable, type:

/usr/local/directadmin/directadmin set forwarder_loop_check 0
service directadmin restart

T27969 new 'force' param to force new keys new

Sometimes you might want to force the creation of new dkim keys. New optional "force" option with the script can be used to overwrite the old values.


./ <domain> (nodns) (force)


<domain>: Required. Name of the domain to enable dkim for.
nodns: Optional.  Prevents adding the keys to the zone.
force: Optional.  Force overwrite of the keys with new values.


FileManager: edit: optional: must_exist=no for error pages (SKINS) new

When viewing; HTM_ERROR_PAGES?

the URLs to edit the 404.shtml (for example) would point to: /CMD_FILE_MANAGER/domains/

The issue arises if the User has deleted or does not have that 404.shtml. The new option: must_exist=no has been added to the action=edit such that the file does not need to exist to open the edit page.

eg: /CMD_FILE_MANAGER/domains/


The check is due to the: fm_allow_binary_edit=0 option, where the file needs to exist to know if it's binary or not. To work-around the error, simply set:

./directadmin set fm_allow_binary_edit 1
service directadmin restart

and the file-exists check will not be done.



added &must_exist=no to all action=edit URLs.


FileManager: Options to uncheck "move to trash" by default new

The FileManager currently has a new Trash feature, and when deleting a file/folder, there is a checkbox for "Move to Trash", which is checked by default. This feature adds a new internal directadmin.conf value: fm_to_trash_default=1

which means the checkbox is checked by default (as it was before). If so desired, you can set this to 0 with: /usr/local/directadmin/directadmin set fm_to_trash_default 0 service directadmin restart

which will have the "Move to Trash" checkbox un-checked by default.


The general json['info]['fm_settings'] array will now include this fm_to_trash_default variable, so json-based file managers can set the default checkbox state accordingly.


File Manager: v2 OS-like controls new

The Evolution skin now comes with a new and improved File Manager. It supports ctrl, ctrl+shift, shift (cmd on mac instead of ctrl) 'shortcuts', drag and drop directly for uploads, drag and drop inside, right click, etc.

It should feel more like an typical operating system than a website and is far more intuitive to use.

ftp_list.php: ftp_list_run_as=nobody: option to change User new

By default, the ftp_list.php always runs as "nobody", when getting a list of files from an ftp server. The ftp_download.php and ftp_upload.php always run as "diradmin" when working in /home/tmp/admin.1234/*.

In some custom cases, you may want to be using ssh keys, or need all 3 scripts to run as the same account, so this change adds a new directadmin.conf option: ftp_list_run_as=nobody

which can be altered to be: ftp_list_run_as=diradmin

if needed (or any other account, if you wanted), eg:

/usr/local/directadmin/directadmin set ftp_list_run_as diradmin service directadmin restart

DNS: named templates: USERNAME token new

The named.db and all dns_*.conf (dns_a.conf) files will now support the "USERNAME" token for all rewrites, assume you have one of these directadmin.conf value enabled: allow_ttl_override=1 or cluster=1

The ttl is enabled by default, so should be for everyone. Eg: You can confirm with:

./directadmin c | grep allow_ttl_override

Compile time: Sep 9 2020 at 14:10:23

The ~/public_html symlink is very often used for quick navigation to the domain's public_html directory. In Evolution, the top-level of directories is obtained with the request:


but because a symlink is a file when lstat is used, it was not shown in the list. This change will include a symlink, if it points to a directory, in the result, but will add "islink" and "linkpath" in the output, eg:

        { "path": "/.php", "dirs": 0, "files": 10, "files_size": 0 },
        { "path": "/admin_backups", "dirs": 0, "files": 0, "files_size": 0 },
        { "path": "/user_backups", "dirs": 0, "files": 0, "files_size": 0 },
        { "path": "/.ssh", "dirs": 0, "files": 1, "files_size": 0 },
        { "path": "/public_html", "dirs": 5, "files": 7, "files_size": 12477,"islink": "1", "linkpath": "./domains/"  },
        { "path": "/domains", "dirs": 3, "files": 0, "files_size": 0 },
        { "path": "/imap", "dirs": 1, "files": 0, "files_size": 0 },
        { "path": "/.trash", "dirs": 2, "files": 0, "files_size": 0 }

where "islink' == 1 allows the skin to indicate that it's a symlink, rather than showing it as a full "directory" (eg: add a red arrow or some other indication on the icon)


Default Change: TTL from 14400 to 3600 new

Lowered the default_ttl=14400 setting down to 3600. You can set it back, if needed, using: /usr/local/directadmin/directadmin set default_ttl 14400 service directadmin restart

Brute Force Monitor: modsec_audit.log Mod_Security (TEMPLATES) new

The BFM will now scan the modsecurity "modsec_audit.log" files (under /var/log/httpd or /var/log/nginx), when each line is json encoded, to block IPs.


This will be OFF by default in the internal directadmin.conf: brute_force_scan_mod_security_logs=0

This is to allow for more testing, although we not had any issues with the json log parser.


./directadmin set brute_force_scan_mod_security_logs 1
service directadmin restart

The entry shown on the BFM page will only show a select number of entries from the full log.

  • host: domain that was attacked (taken from the Host header)
  • id: ModSecurity's unique log ID.
  • uri: /the/bad?request=../etc/passwd

Note that there are 2 versions of ModSecurity (apache vs nginx), and the json log info varies slightly. DA figures this out automatically based on the 2 different brute_filters.conf entries, but will still try and place the ~same data into the 3 vars above, even if the uri format may differ slightly.


either: /var/log/httpd/modsec_audit.log or /var/log/nginx/modsec_audit.log


DA only parses those logs if the custombuild/options.conf has: modsecurity=yes


The brute_filter.list now supports: count_divisor=## which lets you lower the max count needed in order to have it trigger. In this case, we've set it to 2, meaning it will require 1/2 the total number of hits to block. Eg: the default hit count is 100, so this would only need 50 hits to trigger a block.


brute_filter.conf, 2 new entries:


which are type=json lines. The host/id/uri values are take based on the *_tree vars, where the values are those variables are levels down into the json array, the last value being a string. errors to return non-zero exit code new

As many installs are automated, it would be great to automatically know if something went wrong. This change to the directadmin binary, scripts/ and will all relay the exit code, should it be non-zero.


All codes below are possible. The sections just clarify where it might have occurred:

0: success, help
1: must be root to run
    /usr/bin/perl missing
    32-bit install on 64-bit-only OS
    'n' answered for correct OS question
    wrong PHP/CB setup choice
    bad system date.
3: unable to download the scripts/ for this OS (new unsupported OS?)
    unable to download the update.tar.gz
4: update.tar.gz downloaded, but license not valid (possible license issue, similar to 71)
5: cannot find the /usr/local/directadmin/directadmin binary after update.tar.gz extraction
12: openssl.h missing. Ensure pre-install commands were run, before or even during (with option to do so)
56: wrong number of ./ params passed
70: /root/.lid_info did not succeed in being filled for ./ auto
71: error=1 found in the /root/.lid_info (possible license download issue, expired, etc)
80: /usr/bin/wget is missing (FreeBSD: /usr/local/bin/wget) : called via, so code will match it's return code:

1: FreeBSD: Cannot find diradmin after creation
    ' auto' error.  See 71.
    scripts/packages/services.tar.gz download error
    error extracting that services.tar.gz
    call to scripts/ has failed
    custombuild download error
    custombuild build error
    /usr/local/bin/php is minssing
    /usr/local/directadmin/conf/directadmin.conf is missing after the ./directadmin i.
2XX: the return code of ./directadmin i, below.
2XX: ./directadmin i error:
Run this to check: "./directadmin i""
200: re-install, 'n' provided when asked to re-install.
201: installer run as non-root
202: cannot chdir to /usr/local/directadmin
203: cannot read ./scripts/setup.txt as root
204: data/templates/httpd.conf install issue (does not apply to CB2/Modern instals where custombuild/options.conf exists)
205: Tokenizer error from data/templates/directadmin.conf to conf/directadmin.conf
207: Failure in the creation of the admin account.
208: cannot read the newly installed conf/directadmin.conf


BubbleWrap for LiteSpeed (TEMPLATES) new

The LiteSpeed User httpd.conf templates now add: BubbleWrap On|Off

near the end of each VH, eg:

|*if WEBSERVER="litespeed"|
<IfModule Litespeed>
CacheRoot lscache

Where |BUBBLE_WRAP| is swapped with the BubbleWrap On|Off text, based on the user.conf jail=ON setting.


the 4x virtual_host2.conf templates have the |BUBBLE_WRAP| token, as set above.

Large file uploads: error handling fixed

Previously, if a file was too large and one of the conditions happened:

  • content-length exceeded the maxfilesize
  • content-length exceeded free space on /home/tmp

if any error occurred, DA would immediately stop accepting the download, spit out the error message and close the connection. Since the client was still in the process of uploading the file, the browser would not have been listening for any output yet, so the browser would get a connection closed error. Browsers may have even re-attempted the upload, causing 2 full attempts before giving up.

As DA does not support "Except: 100-continue" header, the cleanest solution is to accept the remainder of the data, do not write it to disk, until the client is happy, and then output the proper error message. This is a waste of bandwidth, but is how other web-servers operate, so this brings DA into the norm of behavior.

As with the second condition, above, DA will now pre-check the available space with statvfs in the uploaded area after opening the file descriptor. If it won't have enough space, the file won't be saved to disk (but bandwidth will continue to be transmitted, as above, to allow a clean error output)

Debian: Backup without "domains directory" adds extra -C /home/user fixed

Bug, likely added with this change: directadmin_imap_backup without domains directory throws error

ends up creating a tar backup command that ends in: -C /home/user

without any trailing information being added.

Tar, apparently only on Debian, would throw the message;

/bin/tar: The following options were used after any non-optional arguments in archive create or update mode.  These options are positional and affect only arguments that follow them.  Please, rearrange them properly.

gettext: LC_CTYPE to "" fixed

The LC_CTYPE was set to "C" but changing it to blank "" is required to allow special characters to be properly displayed.


Lower number of SSL key bit-size options fixed

The new support for ECC certificates was previously adding all available options from the output of: openssl ecparam -list_curves

it was found that some OSs produce a very large (and confusing) list of possible choices.

Code has been changed to cap that output to only:

  • secp384r1
  • secp521r1
  • prime256v1

and the "text" will be shown at, respectively:

  • EC-384
  • EC-521
  • EC-256

to help make them look less scary.

Improved checking of forbidden domains fixed

Slight logic change on how the forbidden_domains.list file is checked.

rspamd blacklist/whitelist to use ^$ string termination fixed

Relating to the addition of wildcards to SpamAssassin/Rspamd: SpamAssassin: Allow wildcard using regex in exim filter, user rspamd.conf files

If you were to be blacklisting: *.com

it would imply that would be blocked, which it was. The issue is that it was adding it to the rspamd configs as: from = “/.*\\.com/”

which would have also matched somethingcom@something.else, because the dot needed to be double escaped, and the start/end of the string defined. This change now swaps periods with \\. and adds ^ and $ to the start/end of the string, eg: from = "/^.*\\\\.com$/"

Show User: Additional Bandwidth field missing fixed

Literally "lost in translation". Code was removed during translation. Re-added. Only affects Enhanced. Evolution unaffected.


BFM: wp-login.php from 4x to 50x count multiplier fixed

Increase the Brute-force monitor's count limit for the 302 redirect cases on the wordpress wp-login.php from 4x the BFM limit, now to 50x the BFM count limit. This is to ensure there are no false positives, but still present to prevent DDOS style attacks that may hurt the system.

Use doveadm for CMD_API_POP?type=quota loads fixed

There are 2 types of loads for CMD_API_POP?type=quota

  1. CMD_API_POP?type=quota&

This shows all Users.

If your system has: pop_disk_usage_dovecot_quota=1

For each User in the output, the sub-array data will now have:

usage = 142336
usage_bytes = 142336
quota = 10485760
sent = 5
limit = 10

Where, as doveadm does not return the disk space used (total block usage), but rather size of the files on disk, the usage will be lower than before. Also, the idea of using doveadmn is to greatly reduce load times, as it does not count the data in realtime, but does efficiently keep an up-to-date count of the Maildir.

The result is that the usage and usage_bytes will now always show the same (size of total files on disk), where the 'usage' will be lower than it was before (total size of blocks usage.. actual 'disk usage').

  1. Per-User usage will remain backwards compatible, as the read of one single account is "typically" not an issue (I'm sure there might be exceptions), but the odds of a timeout would be far less, vs the read of the entire list of accounts.

The per-Email call would usually be: CMD_API_POP?type=quota&

unless you now include: &pop_disk_usage_dovecot_quota=1

NOTE: I've included &json=yes in the example below:

The old way without pop_disk_usage_dovecot_quota=1 will still give all old output, as before, eg:

        "imap": "270336",
        "imap_bytes": "154854",
        "inbox": "57344",
        "inbox_bytes": "25573",
        "quota": "10485760",
        "spam": "36864",
        "spam_bytes": "36864",
        "total": "327680",
        "total_bytes": "180427",
        "webmail": "0",
        "webmail_bytes": "0"

but when you add: pop_disk_usage_dovecot_quota=1

(requires pop_disk_usage_dovecot_quota=1 to be enabled in DA itself, which most current boxes probably already have) it should load much faster (most noticeable on very large Maildir accounts with many files), and that would show:

        "imap": "142336",
        "imap_bytes": "142336",
        "inbox": "142336",
        "inbox_bytes": "142336",
        "quota": "10485760",
        "total": "142336",
        "total_bytes": "10628096",
        "webmail": "0",
        "webmail_bytes": "0"

where you'd lose out on the spam, spam_bytes, and so nothings breaks, and all of imap, imap_bytes, inbox, inbox_bytes will report the same value, as returned by doveadm (multiplied by 1024 to give us bytes). Previously, the "inbox" was only for the literal inbox folder, and "imap" was everything else. Now, they're both the sum of everything.


Hash URL: SSH: --create-login-url notifying Users when login_keys_notify_on_creation=1 fixed

With this change: Option: login_keys_notify_on_creation=2 for locked on

Setting: login_keys_notify_on_creation=1

should have blocked the Message System notice on one-time hash login. (If login_keys_notify_on_creation=2 were set, then yes, the notice would have been sent).

Using login_keys_notify_on_creation=1 now properly suppresses the login hash use notice, when it was created from ssh with the --create-login-url method.

FileManager to show mtime fixed

The Filemanager was showing the ctime (last change) of a file/folder, rather than the more commonly used mtime (last modified).


Plugins path to 711 fixed

The /usr/local/directadmin/plugins directory was previously 755. To prevent the listing of the directory, 700 was attempted, was insufficient, so we've settled on 711.


rspamd whitelist/blacklist: normal emails to use ^$ regex match fixed

Previously, a whitelist/blacklist entry would have been: from = ""

changed to be: from = "/^user@email\\\\.com$/"


Clear Message not showing list of auto-fill subjects fixed

With the recent support to gettext, we've dropped the data/skins/enhanced/lang/en/internal folder, as it's no longer needed. In that folder was the file: clear_message_system_list.txt

with data:

2=-- anything --
3=Brute-Force Attack detected in service log
4=is currently down
5=Your backups are now ready
6=An error occurred during the backup
7=emails have just been sent by
8=Warning: The system load average is

which was used to fill the subject auto-fill when typing clearing messages from the Message System.

This resulted in an empty list. This has been fixed by gettext-izing this data into the binaries. If you need to translate them, the string values (=right side) are now visible in the data/lang/internal.pot file


LiteSpeed: Always define php handler : REVERTED fixed

UPDATE: Sept 15, 2020: with the changes below, errors were being thrown: "Your PHP installation appears to be missing the MySQL extension which is required by WordPress."

The changes below are no longer included in the DA binaries, updated on: Compile time: Sep 15 2020 at 15:11:10


Requested by LS support: always define the php1/php2 handlers in the User httpd.conf files. Previously, it would have only been set if php versions were swapped .

Affects the setting of tokens:


which will be set anytime "lsphp" is enabled in the CustomBuild options.conf.

User creation/modification: ubandwidth=no | OFF fixed

As the "ubandwidth" and other u* options are checkboxes, simply passing them triggered DA to assume they were set. However, setting ubandwdith=OFF (or =no) for example, would not be loggically correct in that DA would still set it to be unlimited.

This fix adds a check any u* option, if passed and set to "OFF" or "no", the checkbox will be ignored.


Bubblewrap jail: sendmail delivery to use smtp:587 fixed

Because the real exim lives outside of the jail, things like php mail() commands cannot access it from inside the jail. They can connect to port 25, so we've crafted a custom /etc/exim.conf to be visible inside the jail, so all deliveries to the jailed exim get send through port 587.

  1. exim needs to be re-built, updated and jailshell 0.3+ installed:
cd /usr/local/directadmin/custombuild
./build update
./build set eximconf yes
./build jailshell
  1. /etc/exim.jail files need to be generated:
echo "action=rewrite&value=jail" > /usr/local/directadmin/data/task.queue
/usr/local/directadmin/dataskq d

Trash: get list from true data, in case files were delete with full quota fixed

If a User moves a file to trash while they have full quota, the .trashinfo file cannot be viewed. Code changes to get the listing from the true data folder: ~/.trash/files/*

instead of: ~/.trash/info/*.trashinfo

The same data will be obtained if both files exist. But if only the ~/.trash/files/file.txt exists, then this will show the file (without specific data/restore info) and allow the file to be manually removed. It will not be automatically removed.


BUG: found/fixed on Sept 10, 2020 where directories were not shown.

LetsEncrypt: delete after success fixed

The file will end up being set to 0 should it try 5 times without success. If another attempt does go through later on (manual request through GUI or ssh), this change will ensure the retry file is fully delete as this may have blocked subsequent renewals.


Do not install proftpd.conf with ./directadmin i. Rely on CB fixed

Custombuild installs the proftpd.conf when proftpd is installed (the default is pure-ftpd anyway). But if ftpd=proftpd was chose, CB would have installed it's proftpd.conf followed by DA installs the templates/proftpd.conf version. Code changed to NOT install the proftpd.conf via DirectAdmin, and simply rely on CustomBuild to do it for us.


Ensure no php-fpm#.conf is php=OFF in user.conf fixed

When php=OFF is set in the user.conf, do not create a php-fpm#.conf file for the Uesr. Also ensure it's deleted during a rewrite if the User's php is not ON.

Also hide the php version selector if the does not have php=OFF

Enhanced Skin: Default to en lang files if custom lang is missing translation fixed

The internal language translations already did this, so do not apply here. This change only applies to the Enhanced mode of translations, where it looks for a matching .html file in the lang/en folder. The change is when the custom lang does not have said matching .html file, so it falls back to the "en" copy, should it exist. This prevents "none" from showing up, instead showing the correct English version of the string.

Optimization: string to json fixed

Swapped the result buffer to use our internal String class, which saves recounting the buffer length each time something is appended. This was implemented while debugging slow load time for the json=yes action=edit in the File Manager for "larger" files (this one was 0.6M)

Enhanced is not affected, as it uses html encoding, directly as it reads the file.

Further optimizations for this might be to read the save the json-encoded string, directly as it's read from disk. This is an edge case (only 1 report), so will omit for the time being.

Other options would be to read the html-encoded TEXT token, without json at all, eg: /CMD_FILE_MANAGER/index.html?action=edit&load_token=TEXT

where TEXT is the value within the Enhanced textarea, which is already html-encoded. With the load_token=TEXT option, the skin an all other tokens are not shown. Only the contents of |TEXT|

DNS: Pointers: Duplicate records with subdomain pointer cause issues fixed

Introduced here: Domain Pointers option to receive duplicate dns entries from master domain (SKINS)

when adding a record to a domain, there is an option to duplicate it to any domain pointers below it. With non-absolute records, say "www", this was fine, but extra code was needed if a full value was specified, say:

where the would need to receive:

This worked fine, however if the pointer was (below this caused issues when editing the pointer's zone directly. Eg, adding a TXT record with left-name:

in the zone ended up with: as DA was trying to snag the "subdomain" part of the zone to prefix to the pointer's zone.

Code changed to never do this subdomain swap if the end of the left-name ended with the current zone name. So adding matches the pointer's zone name, thus is not touched.

In effect, adding: to the master, would result in adding the exact same record to both and's zones.


Do not allow CMD_SSL access if is not enabled in the given domain fixed

Should you access /CMD_SSL? (or /user/ssl in Evolution), when ssl=ON is not present in the file, you'll get an error:

Could not execute your request SSL is not enabled for this domain

In which case, you'd go to your "Domain Setup" area to enable SSL prior to managing your certificates.

With Enhanced skin, you'll get a direct URL below to: /CMD_ADDITIONAL_DOMAINS?action=view&


ClamAV: freshclam has no reload for CMD_SHOW_SERVICES?json=yes fixed

Remove "reload" option for the actions list of the "freshclam" service, as it does not exist.

File Manager: action=parent_tree to respect filemanager_du=0 fixed

The filemanager_du=0 can be set either via the directadmin.conf, user.conf, or via GET. The call to: /CMD_FILE_MANAGER?json=yes&filemanager_du=0&action=parent_tree&path=%2F

would expect to not recursively count each child directory usage. This fix will now ONLY show the directory (or dir listing for the current path.

Eg:, the above path=/ would show something like this, where the "dirs", "files", and "files_size" are NOT included in the output to greatly speed things up.

        { "path": "/.php" },
        { "path": "/admin_backups" },
        { "path": "/user_backups" },
        { "path": "/.ssh" },
        { "path": "/public_html" , "islink": "1", "linkpath": "./domains/" },
        { "path": "/domains" },
        { "path": "/Maildir" },
        { "path": "/imap" },
        { "path": "/.trash" }

doveadm expunge fails on overquota: fallback to old method fixed

If a User is over quota and has passed their grace period, the doveadm expunge call will fail and the emails will not be removed. If the User is over quota, but within the grace, the emails will still be removed, but the index re-write will still fail.

The solution is not the error, and use the old purge method as a fallback in either the above events.


Email::doveadm_expunge: running: /usr/bin/doveadm expunge -u '' mailbox 'INBOX' all
dovecadmn expunge returned an error: doveadm( Error: Mailbox INBOX: write(/home/fred/imap/ failed: Disc quota exceeded
doveadm( Error: Syncing mailbox INBOX failed: Mailbox INBOX: write(/home/fred/imap/ failed: Disc quota exceeded


Last Updated: