Version 1.62.0

Released: 2021-06-09

User selectable Nginx templates (PRO PACK) new

New command:


Feature allowing Users to select from pre-made templates to be inserted into the nginx server entries for common scripts like WordPress. List of templates can be managed through DA.

This is a JSON-only feature (Enhanced will not have it, Evolution will have it) It will apply only to domains, and not to subdomains (similar to E-Mail accounts) Should you need an nginx template on a subdomain, create it as a full domain instead.


CMD_ADMIN_SSL (Pro Pack) new


Admin Level functions for overview and management of all User/Domain certificates, as well as hostname certificates.

Enhanced Skin: CMD_ADMIN_SSL Evolution skin: /admin/ssl

as well as back-end automatic ssl certificate generation based on poll frequency (to allow for domain to eventually resolve).

Read more


CMD_UNIT: Nginx Unit for other handlers (PRO PACK)(SKINS)(TEMPLATES) new

Read more

Cluster: cluster_ip_bind=NULL or cluster_ip_bind= new

In the event that you're on a lan and lan_ip= is set, or you want DA to bind to a specific internal IP address, you can now tell DirectAdmin to either not bind to anything, or bind to a specific value, for all Clustering (Multi-Server Setup).

Default Change: set_php_bin_path_in_crons=1 set_php_bin_path_in_shell=1 improved

The existing feature documented here

allows the User's crontab to use the same php version as set in the DA GUI. This change enables:


by default for all systems. Only re-saving a cron will cause it to take effect. If needed, it can be disabled with:

/usr/local/directadmin/directadmin set set_php_bin_path_in_crons 0
/usr/local/directadmin/directadmin set set_php_bin_path_in_shell 0
service directadmin restart


Databases: option to not escape db name new

Read more


Command line key/URL generation for CLI API calls. improved


For how it currently works, refer to the documentation provided here


--cli-command will be dropped.

Instead, we'll fall back to using our login key system, but with useful tools for cli.

OLD: EVERYTHING BELOW WILL NOT BE USED and dropped from the binaries (was never in production)

./directadmin --cli-command command=CMD_*

ALPHA: This code requires testing prior to production use. It's a front-end wrapper to simulate the socket/request parser for port 2222. Not all end-points have been tested.

Root access to run any CMD_ call (GET or POST) via cli. json=yes is always assumed for the input/output, passing it is not required, but you may need to add if the output is not in json (please report any such cases)


./directadmin --cli-command command=CMD_SYSTEM_INFO user=admin

Will dump the CMD_SYSTEM_INFO command, while running as admin.

For GET/POST, add:


the data passed in the GET/POST request will be passed in the data='' value, eg:


List all email accounts for this domain:

./directadmin --cli-command command=CMD_EMAIL_POP user=admin method=GET data='{ "GET": "" }'

Where the data array will be "GET" and/or "POST", added to the json. The data GET/POST value can be a URL encoded string, or can be json (DA will figure it out and parse as needed).

NOTE: shells might not like the & character should you be using it in your GET/POST. DirectAdmin will URL decode normally, so use %26 instead of the ampersand & character, eg: You might hit an error like this if you're hitting the issue:

"result": "Request::set_cli_request: error parsing json line: String did not find closing '\"'

assuming you're using valid json.

To keep the data safe, you can use: data=stdin

and pass your json {} to stdin, followed by an EOF character (ctrl-d when doing it manually)


This is a stdin sample, can be piped to DA however you wish. In this example, we'll create a file called: CMD_API_POP.json (name doesn't matter)

With contents:

        "POST" : {
                "domain" : "",
                "action" : "create",
                "user" : "cliuser",
                "passwd" : "scretpAss1!",
                "quota" : "10"

and make the request to DA as follows:

[root@es6-64 directadmin]# ./directadmin --cli-command command=CMD_API_POP user=admin method=GET data=stdin < CMD_API_POP.json
        "result": "cliuser",
        "success": "Email account created"


For added security, you may not want command, user, method on the command line. These values can be included in the stdin/data, instead of the command line, eg:

        "method" : "POST",
        "command" : "CMD_API_POP",
        "user" : "admin",
        "POST" : {
                "domain" : "",
                "action" : "create",
                "user" : "cliuser",
                "passwd" : "scretpAss1!",
                "quota" : "10"
[root@es6-64 directadmin]# ./directadmin --cli-command data=stdin < CMD_API_POP.json
        "error": "Unable to create email account",
        "result": "That user already exists\n"

NOTE: command line arguments will override any data/stdin arguments. This may be useful if you


If the wrapper is able to correctly simulate the input, and all data is parsed and sent to the back-end, exit code 0 will be returned from the directadmin binary. This includes "error" states for the json, eg: the above That user already exists returns exit code 0 as the wrapper was successful. Check for "error" in the JSON output, but it may vary based on the CMD call made (see API documentation for the expected json output)

However, any wrapper errors, such as json parsing, missing method/command/user, etc.. will exit with code 14. The output there "should" still be in the same json "error" format.

package_write_pre/, package_delete_pre/, package_rename_pre/, package_copy_pre/ new

Custom scripts, triggered for actions on packages.. Env vars will be the contents of the package file, plus:

package_filename=/usr/local/directadmin/data/admin/packages/NAME.pkg OR /usr/local/directadmin/data/users/RESELLER/packages/NAME.pkg

A non-zero exit code will abort the save if it's a "pre" script. The exit code of post scripts do not affect the result. Any output is added to the result.

Read more

Global IP Assignments new

Creation of Users now lists global IPs

Read more

Default change: tally_after_restore=2 improved

Previous default was: tally_after_restore=1

which runs a full tally after any full Restore. The notice about the restore being successful doesn't not get sent out until after the tally finishes (in the same thread).

The new default will be: tally_after_restore=2

which pushes the tally request to the task.queue for a separate dataskq call, allowing the restore to exit immediately. The only "downside" is the slight lag in stats being updated, but will after the tally finishes (which time can vary depending on the amount of data it needs to process).


DNSSEC: Ability to clear keys, use non-signed zone new

Relating to the DNSSEC feature for both Admin and User Level DNS areas, this functionality adds support to delete all keys and revert to a non-signed zone.

Read more

Failed 2FA to ip_blacklist improved

The "two-factor auth" and "security questions" already had counters for the number of max attempts.

However, this change adds to this by counting each failed 2FA/question in the 'failed_logins' file (where wrong passwords are counted). This means you must get a valid password and valid 2FA/Question within the "brutecount" limit, else you'll be put on the ip_blacklist.

Login triggers are also reworked to only trigger after both the user/pass login and 2FA/question are answered correctly. Previously, the trigger would happen after the valid login, before the 2FA/questions were answered. Triggers include:

  • checking to ensure the task.queue is being processed (random)
  • check for old custombuild/versions.txt (random)
  • clear the data/admin/ip_access/ folder
  • add to "login history"

Default: show_info_in_header=0 improved

Relating to the ability to shut off the version and info in the headers documented here

this will now change the default to:


BFM: respect CSF CC_IGNORE country codes improved

The CSF config has a setting:


which lists country codes to be ignored based on the location of the IP. If you have CSF installed, the Brute Force Manager will now respect this list, using the command:

csf -i

to get the required info about the IP address, thus allowing that country to be skipped.


Nginx locations: new internal structure for tokens (TEMPLATES) new

As nginx locations are quite rigid in terms of not allowing conflicts/duplicates, we've hit the point where in order to allow 2 features to use the same location block, internal management is now required.

Read more

Admin Restore: allow Reseller creator override new

For calls to the Admin Level Backup/Restore: CMD_ADMIN_BACKUP

When a restore is issued, an additional value: reseller_override=fred

can be passed. "fred" must be an Admin or a Reseller.

Read more


User Backup: select included domains new

Users can now select only the domains they wish to backup.

For backwards compatibility, if zero domains are selected/passed, DA will include all domains.

In addition to the current User Backup to CMD_SITE_BACKUP, include:

For any desired domains to be included.


The paths that will be excluded for any non-included domains:


Where the tar/compression command used will be changed from (abbreviated) domains imap to: domains/ imap/

The "backup" folder won't create the, hence it will still use "backup" during compression.


When a User creates the backup with selected domains, the usual items are added to the task.queue, in addition to a colon separated list:



GUI: max_per_email_send_limit user.conf override new

The user.conf override option for: max_per_email_send_limit

now has a GUI when on the details for a given User.

For enhanced, the page: CMD_SHOW_USER?user=fred

will show an extra row, just below Received Emails, called: Max limit User can set per E-Mail

If you're an Admin, you'll be able to modify this value. Setting a number saves max_per_email_send_limit into the User's user.conf file. Setting it as a blank value deletes the max_per_email_send_limit from the user.conf.


blank/empty string: The default: relies on the same setting from the Admin Settings page. -1: overrides to -1, meaning the max value a User can set will match /etc/virtual/user_limit 0: Unlimited (not recommended). Although an E-Mail could have unlimited sends, the send limit cannot exceed the DA User's limit. positive integer: The numerical limit a User would be allowed to set as the max send value for an E-Mail.


method: POST
max_per_email_send_limit=<any text>
max_per_email_send_limit_value=VALUE (blank or -1+)


When viewing: CMD_SHOW_USER?user=emailuser&json=yes

the output for this setting might look like this:

			"setting": "max_per_email_send_limit",
			"usage": "48",
			"max_usage": "200"

where 48 is going to show the user.conf value: max_per_email_send_limit=48

where 48 could be any of:

blank  (reverts to the global limit)
-1, 0, positive integer

and where max_usage=200 refers to the value in /etc/virtual/user_limit. In this case, it's not the max an Admin could set in the filed, but just a reference for what using the blank value would be. The setting can be confusing, so show the options as clearly as possible. eg, -1 would be "dynamic" as it would follow the /etc/virtual/user_limit, if that file is changed.

EVO2027 T30877

Per-User user.conf override for max_per_email_send_limit new

Relating to the directadmin.conf option: max_per_email_send_limit

You can now add a value, eg: max_per_email_send_limit=2000

to a given User's user.conf file to allow it to override the directadmin.conf value.

This is useful if you have one User you want to allow to set a high per-Email send limit, but not want to allow all DA Users the ability to set a high per-Email send limit. You'll need to manually add the max_per_email_send_limit value into the given User's user.conf file for it to take effect.

The single sign-on tool for the URL path /roundcube now has a directadmin.conf option which can be changed. The internal default is:


So if you've got your /roundcube setup with /webmail, this lets you have the click within DA to redirect this value.

Single FileManager copy/rename 'new_path' to new directory improved

The CMD_FILE_MANAGER calls to:


for single "old" to "filename" copy/renames, now supports an optional option:


where, if it's not passed, the "path" is used, either via path variable ?path=/some/path, or extracted from the CMD_FILE_MANAGER/some/path, and the "new_path=/some/other/path" can optionally be passed to have the single copy/rename affect some other folder. This is chrooted, as before, so it's going to be relative to the User's home (nothing new there).

Note that an "action=multiple" call (when selecting many files/folders) has not been changed. For multiple files, any "path" you specify will be where they end up, as their full paths are already stored in the clipboard (or passed as a dynamic/realtime clipboard).


Skin Customization: favicon, symbol, symbol2 improved

Relating to the existing feature documented here

which allows for custom logos in the skin (logo,logo2),

This new feature extends this, now to 5 values (from 2): which:


which adds the global tokens accordingly, eg:


and the data/skins/SKIN/skin.conf adds the setting of those request values (for example):


NOTE: /favicon.ico This WILL respect the above files, but can loggically skip the lookup for the IMG_FAVICON value in files_*.conf because any customization and it would point back to the same file:


Where USER is determined by:

  • If logged in, Reseller/Admin: user own value
  • If logged in, User, use user.conf:creator value
  • If not logged in, first Admin from admin.list is used for USER, viewed on the login page before login.

Like other logo/logo2 value, the skin_customization directory is fully read, and XX is determined by "favicon.*" for any type/extension. You may still override the "shortcut icon" value in the html itself using your /IMG_FAVICON which would let you point: files_user.conf (for example): IMG_FAVICON=images/favicon.png thus allowing IMG_FAVICON to be customized.


For the current global skin (./directadmin c | grep docsroot=), all 5 of the above IMG_* files, set by the 5 token names in that docroots skin.conf, if present, will be allowed on the login page, without authentication. These 5 IMG values will respect the customization, similar to the /favicon.ico request. This is only allowed for IMG_ formatted requests. Anything else will use the previous non-login rules.


	"name": "evolution"


Default change: brute_force_scan_mod_security_logs=1 improved

Previous default was:


where installing mod_security would block bad requests, as normal, but the Brute Force Monitor (BFM) would not block those IPs in the firewall. This change now enables the BFM to scan for those mod_Sec entries, and will block the attacking IPs if the count is high enough. Note that mod_security filter in brute_filters.list has a divisor of 2, so if your BFM blocks at 20 failed logins, a given IP would need 40 mod_security blocks before the BFM blocks that IP.

New default:


Remove duplicate php paths: set_php_bin_path_in_crons=2 new

New option for set_php_bin_path_in_crons, where setting it to 2, eg:

/usr/local/directadmin/directadmin set set_php_bin_path_in_crons 2
service directadmin restart

will reduce any duplicate /usr/local/phpXX/bin entries from the crontab's PATH value. Eg, if you have:

crontab -u fred -l | grep PATH

where there are 2 entries for php 7.0 and 7.4, you can clear out the 2nd entry, regardless of the version set, by using 2, and issuing a rewrite:

cd /usr/local/directadmin
echo "action=rewrite&value=httpd&user=fred" > data/task.queue.cb; ./dataskq d1000 --custombuild

(or without &user=fred for all accounts) and it will reduce it down to:


in the crontab.

This setting should only be used temporarily, and we would recommend setting it back to 1 once you're done clearing any duplicates.

Default change: subdomain_force_redirect=0 improved

Relating to the User Level feature that allows forceing » to (or vice versa) This change, new internal directadmin.conf option:


excludes subdomains from this redirection. The logic being that we rarely intend for the redirection to affect subdomains, eg: »

is rarely desired.

Thus, we've added:


where any www or non-www redirection for domains or pointers will not longer affect subdomains. (Where a subdomain is created under a domain, and does not refer to subdomains created as "full domains").

If you do need subdomains to redirect to www, then enable the setting globally:

/usr/local/directadmin/directadmin set subdomain_force_redirect 1
service directadmin restart

and the next rewrite of the User httpd.conf (or other server User config) will be updated with the change. To update all User configs, type:

/usr/local/directadmin/custombuild/build rewrite_confs

Option to suppress license update notices new

If you no longer want to get the notices with subject: License File has been updated

this can now be disabled:

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

Where the internal default is on, eg: notify_on_license_update=1

Any failures will still be sent.

letsencrypt_request_results_to_admins=0 new

New option, disabled by default:


where, if set to 1, any LetsEncrypt request results will be sent to the Admins, and not to the User's Message System, eg:

/usr/local/directadmin/directadmin set letsencrypt_request_results_to_admins 1
service directadmin restart

This refers to manual User Level » SSL Certificate requests, and not renewals which have their own set of controls.


Optional: letsencrypt_renewal_notice_to_admins=3 for creators improved

If there is a renewal failure, previously: letsencrypt_renewal_notice_to_admins=1 would also send the notice to the Admins.

This change allows for:


where the setting will be a bitmask of:

  1. Admins
  2. account creator (Reseller)

this for both, you'd use (1 & 2 == 3): letsencrypt_renewal_notice_to_admins=3

for only Admins, you'd use: letsencrypt_renewal_notice_to_admins=1

and only the creator of the account, use: letsencrypt_renewal_notice_to_admins=2

This setting is in addition to the actual account messages, and does not block the account's message.

User cgroups: cpu/memory/io per-User resource control (SKINS)(LANG)(PRO PACK) new


This feature is part of DirectAdmin Pro Packopen in new window

This new feature relies on "cgroups v2", which is a kernel option.


  • modified: data/skins/enhanced/files_reseller.conf HTM_CGROUP=reseller/cgroup.html

  • new file: data/skins/enhanced/reseller/cgroup.html See file for table data/tokens

  • modified: data/skins/enhanced/reseller/create_customized_user.html

  • modified: data/skins/enhanced/reseller/modify_user.html

  • modified: data/skins/enhanced/reseller/show_user_package.html

  • modified: data/skins/enhanced/admin/create_customized_reseller.html

  • modified: data/skins/enhanced/admin/modify_reseller.html

  • modified: data/skins/enhanced/admin/show_reseller_package.html

6 files add:


Before CUSTOM_ITEM_1 tokens line.


  • modified: data/skins/enhanced/lang/en/admin/create_reseller.html
LANG_CGROUPS_RESOURCE_LIMITS=Linux Control Groups - Resource Limits
LANG_BLANK_FOR_UNLIMITED=Blank value for unlimited

Read more


Optional: set Return-Path for diradmin E-Mails new

Feature, when enabled, allows you to override the default in the Return-Path, and set something else, eg:

/usr/local/directadmin/directadmin set diradmin_envelope
service directadmin restart

By default, this is disabled and relies on your hostname being setup/resolving correctly.


Variable: letsencrypt_background_default=auto (SKINS) new

LetsEncrypt certification creation already allows for a skin option during the POST:


where skins would use auto and let DA decide what to do for the actual calls to letsencrypt. For auto, if it's a wildcard, it's always yet. For non-wildcard, it's based on the letsencrypt_foreground_http_max=10 variable.


This new option lets you pass a variable to the skin via directadmin.conf, specifying which default option should be used. New internal default;


where you can change this to be yes or no, eg:

/usr/local/directadmin/directadmin set letsencrypt_background_default no
service directadmin restart

to always have all LetsEncrypt requests work in the foreground. It requires that the skin actually support this option and properly read the token.




<input type=hidden name=background value="auto">

to be:

<input type=hidden name=background value="|BACKGROUND_DEFAULT|">


Viewing the SSL certs page will also include:



Option: LetsEncrypt success: provide full request output new

A few DA versions ago, a change was made that only shows a basic success message for LetsEncrypt requests. The logic was that there is no need to cause confusion with extra info if everything went well.

This option allows the full output to be shown again upon success. To enable, type: /usr/local/directadmin/directadmin set letsencrypt_success_full_output 1 service directadmin restart


Admin IP Manager: List of IPs direct from device (JSON) improved

The call to:


will now include an extra array holding all IPs listed in the device, totally independent of what DA configured.

The device_ips will have a list of devices in the "devices" array. Each IP in that device will have:

  • a numbered netmask, eg:
  • a CIDR format bitmask, eg: /24
  • and the ifa_flags for this IP address.

To know what the flags mean, do a bitwise & on them using the device_ips[ifa_flags] array in a loop. eg:

if (69699 & 1)
    print "Interface is running."

should you need to display this information. See below for the list of masks, but they'll be included in the json output.


The feature will appear in: Admin Level » IP Manager » "Devices" tab.

eg: /admin/ip-manager


Sample output:

					"bitmask": "/24",
					"ifa_flags": "69699",
					"netmask": ""
					"bitmask": "/24",
					"ifa_flags": "69699",
					"netmask": ""
					"bitmask": "/64",
					"ifa_flags": "69699",
					"netmask": "ffff:ffff:ffff:ffff:0:0:0:0"
					"bitmask": "/128",
					"ifa_flags": "65609",
					"netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
					"bitmask": "/8",
					"ifa_flags": "65609",
					"netmask": ""
			"1": "Interface is running.",
			"2": "Valid broadcast address set.",
			"4": "Internal debugging flag.",
			"8": "Interface is a loopback interface.",
			"16": "Interface is a point-to-point link.",
			"32": "Avoid use of trailers.",
			"64": "Resources allocated.",
			"128": "No arp protocol, L2 destination address not set.",
			"256": "Interface is in promiscuous mode.",
			"512": "Receive all multicast packets.",
			"1024": "Master of a load balancing bundle.",
			"2048": "Slave of a load balancing bundle.",
			"4096": "Supports multicast",
			"8192": "Is able to select media type via ifmap.",
			"16384": "Auto media selection active.",
			"32768": "The addresses are lost when the interface goes down."

T31486 EVO2048

SPF: Add server IPv6 by default (TEMPLATES)(SCRIPTS) new

New internal default setting: dns_add_spf_ipv6=1

Requires ipv6=1 enabled. To confirm, look for ipv6=1 output:

/usr/local/directadmin/directadmin c | grep ^ipv6=

To disable, run:

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

When a new domain/zone is added or a zone is reset, the TXT/SPF records for v=spf1 will now include:



changes to 2 template files:

|DOMAIN|.="v=spf1 a mx ip4:|SERVER_IP||EXTRA_SPF||SPF_IPV6| ~all"

Where 2 new tokens are added (blank or set):

SPF_IPV6= ip6:the:ipv6:of:the:server     #note the space at the start of the token value for spacing in the v=spf1 value.


New script at:


which currently makes a remote wget call, forced to use IPv6 with --inet6-only to obtain the IPv6 based on the returned value. This may change, but appears to be reliable.

The returned value, if valid and correct, is cached in the DA child process for up to 1 hour (prevents hammering wget).

Note the parent call does not cache it, just the child, but is mainly useful for dataskq calls when it's used many times in one process.


The server ipv6 will be logged in the backup/user.conf during backup. Upon restore, if the new box has the feature enabled, the old value is swapped with the new server's value in TXT/SPF records.


Delete Users in background if too many DB Users new

New internal default option: background_delete_if_num_db_users=500

where, if the total number of MySQL Users being removed during DA User removal is greater than 500, all Users being deleted will be done in the background.


You can disable this check with:

./directadmin set background_delete_if_num_db_users 0
service directadmin restart

This is related to: background_delete_size=10240

where the same thing would happen if Users being deleted amount to more than 10 Gig in size (based on the last tally)


Template: suspension_reason.txt now only used from internal list for translation improved

Change to internal translations only for the template (which will no longer be read in):


The fill will remain, so you can copy it to data/templates/custom/suspension_reason.txt. The custom/suspension_reason.txt instance WILL be read in. This would be useful if you need to add or remove reasons (copy the whole file, new list).

The texts from the internal (default) variant, will now be visible in the internal.pot for standard gettext translation.

per-domain nginx=1 (disable proxy) for nginx-only processing (SKINS) new

Read more

Cache for ./directadmin --DocumentRoot new

The call to ./directadmin --DocumentRoot figures out the DocumentRoot values for both http and https, for each domain, subdomain, and pointer. These values are very dynamic and are altered by various different areas, so the only way to know the true value is to fully compute it. Doing this on the fly for all VH on the box can take several seconds (in the scenario triggering this change: 18s).

To speed things up, we'll create a new file:


which is created if the file does not exist, or the cache.json file is older than the current httpd.conf (or nginx.conf, openlightspeed.conf). If the cache is newer than the httpd.conf, this cache is used for the --DocumentRoot call, greatly speeding it up. If the cache is not there, or older, then it's computed normally with the --DocumentRoot call, but written just as the output is generated by the call, ready for next time.


hooks: special_exit_code 42 new

This feature will allows for the script output to be forced to the visible result, even if it's not normally shown.

Internal directadmin.conf default:


Read more

--create-login-url: _hash_expiry_minutes=4320 instead of internal hardcoded new

This new internal option simply lets you alter the internal default time:


Read more


JSON input: all forms improved

BETA Ability to pass JSON in POST data instead of url-encoded data.

This will open up more options and cleanliness for submitting data to DA, without using the URL encoded / form data format.

Load Average in System Information new

Calls to the System Information can now support load average, enabled by default.


The internal default will be:


To disable, type:

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


There are no skin changes for Enhanced skins. The table will automatically include the new row, as needed.


Will include top-level array items, eg:

load_1 = 0.57
load_15 = 0.81
load_5 = 0.81


Will include a top-level array called "load", with 3 fields, eg:

		"load_1": "0.17",
		"load_15": "0.75",
		"load_5": "0.64"


CMD_JSON_LANG: Show info on most recent messages/tickets improved

For calls to:


where it must have:


Additional JSON output will be included:

			"from": "diradmin",
			"id": "000005329",
			"name": "Message System",
			"new": "yes",
			"priority": "30",
			"status": "open",
			"subject": "Warning: The disk usage for one or more of your partitions is running low",
			"timestamp": "1617300055",
			"type": "message",
			"user": "multiple"
			"from": "diradmin",
			"id": "000005330",
			"name": "Message System",
			"new": "yes",
			"priority": "30",
			"status": "open",
			"subject": "Warning: The disk usage for one or more of your partitions is running low",
			"timestamp": "1617300061",
			"type": "message",
			"user": "multiple"
			"from": "diradmin",
			"id": "000005331",
			"name": "Message System",
			"new": "yes",
			"priority": "30",
			"status": "open",
			"subject": "User quotauser has used up 0.0% of their bandwidth and 680% of their allocated disk space",
			"timestamp": "1617300139",
			"type": "message",
			"user": "multiple"

The recent_tickets array hold 3 arrays, one for each of the last 3 messages/tickets.

Where the contents of each ticket ID will be:

id=12345   #The tail -n1 of the User's ticket.list file.
new=yes|no  #seen or not, taken from tickets.list value.

Everything else, a dump of data/tickets/123/12345/000.conf

Note, it will only ever show the last entry from the tickets.list. If an old ticket has a new reply, where there are other new messages/tickets, that reply won't be reflected. It only shows the details of the higher-numbered value from the tickets.list (the last entry)


DNS: Show missing records if deletion requested new

Deleting a missing DNS record does throw an error. The logic behind this is that the desire for it to be gone was already true. This is especially useful when deleting multiple records where only some are missing, we don't throw an error so they can confirm the requested records are all gone.

However, it may be useful to know that some records were indeed missing prior to the removal. This change will add a warning message for each missing record, eg:

www A did not exist

after the successful text:

Records Deleted

Either way, no error will be thrown after all records are confirmed gone from the zone.


Automatic SSL Certificates - One Certificate Per Host (SKINS) new


New feature which will automatically attempt to install and manage certificates for newly created domains, subdomains, and domain pointers. This will make use of the "one certificate per VirtualHost" concept, allowing for cleaner additions/removals (eg: subdomains), without the need for a new request on the main domain or other certificates. A new domain will attempt a wildcard cert, meaning subdomains created later do no need a certificate. However, if dns is external, it the domain would fall back to,, meaning a new would retrieve it's own certificate as well (attempting dns first, falling back to http letsencrypt requests)

Writes to httpd.conf files will use the snidomains file to determine which certificate to use, assuming a cert has not already been explicitly set by the User. (The snidomains lookup only applies if the domain is using "Shared Server Certificate", and the domain is not literally in the shared server cert)

Dovecot sni configs will also contain wildcards, instead of the full list of smtp, mail, pop, etc, if a wildcard cert is available. Pointers would also get their own dovecot sni config file in a similar manner.

The general change (when "Shared Server Cert" is selected) is that certificates are no longer linked to a domain. They're on a per-host basis, allowing any area that needs them to use them.

This system also allowed for cross-User certificates, assuming there's a match (eg: * owned by fred, but bob has domain, no need for a new cert). However, the cross-User cert setting is only available in the DirectAdmin Pro Pack.


  1. LetsEncrypt is installed and enabled:

If not, enable/install LE with this guideopen in new window

  1. Domain pointers should be on their own VirtualHost:
  1. One SSL certificate per VirtualHost should be enabled:
  1. The background retry check must be enabled:
  1. The /etc/virtual/snidomains must be in use:


In order for a domain (and all child hosts) to be able to use this feature, it must be set to "Shared Server Certificate" on the "SSL Certificates" page. it must not be set to have a pasted cert, as this will be a fully separate system than the existing certificate requests.

You can check any of the above settings with the ./directadmin c command, eg:

./directadmin c | grep mail_sni

Most should already be enabled by default, but some on older boxes might not have them enabled, eg:

./directadmin set mail_sni 1
./directadmin set pointers_own_virtualhost 1
./directadmin set admin_ssl_cert_per_vh 1
./directadmin set admin_ssl_check_retries 1
service directadmin restart


Once enabled, the simplest way to disable the feature is to disable background polling:

./directadmin set admin_ssl_check_retries 0
service directadmin restart


Assuming all of the above are enabled, and the current domain has ssl enabled, any new domain, subdomain, or pointer will create a retry file, which the background dataskq will find and execute.

The system REQUIRES that a domain's certificate is using the "Shared Server Certificate".



The /etc/virtual/snidomains file is the core index for this new system; all areas branch from the values in this file, thus it must be correct. If you need to fully rebuild the file, see the task.queue call below, keeping in mind that it's a slightly different format using wildcards, vs before which listed each specific mail host explicitly. (eg:

The format is:

Where "host" can be any host, like, or *,, etc... which is the lookup that all services use (exim, directadmin:(2222, apache+dovecot configs)) The user (eg: fred) refers to /usr/local/directadmin/data/users/fred and the last domain/host value is the value in that User's domain directory, eg:


For example, this snidomains entry:


would mean, that for the given service, if is requested, the lookup would point to: /usr/local/directadmin/data/users/fred/domains/ (or, depending on which service is asking)


This is the request file. It's essentially similar to a POST request, used back the dataskq to decide how the certificate should be created. This file is deleted after a request is successful.


This lets the dataskq know when it's allowed to try the request next. The frequency decreases over time, based on the directadmin.conf option:


See the Admin SSL feature for more information on how admin_ssl_poll_frequency works here

This lets a value retrieve a certificate as fast as possible, but also allowed for longer periods if the DNS is still being changed to the server.. while keeping the number of requests to a minimum.

This file is deleted after a request is successful.

Service certificate files

The actual certificate to be used by services:

  • apache,
  • exim,
  • dovecot,
  • directadmin,
  • pure-ftpd.

for secured SSL connections, using SNI.


Most of the work and changes is all controlled silently by the back-end. However, there might be cases where a User needs to intervene:

  • Trigger a request for an already-existing domain/subdomain/pointer
  • Cancel the future retry attempts for a given host.
  • force a retry "now" instead of waiting for the next retry attempt


Relating to the skin tables below, they can be assembled, with even more detail, using the json output from:


where new json values are included: CAN_AUTO_SSL_CERT=1 It is set if this feature is even available for use. (We'll rely on SERVERCHECKED="checked" being set to offer the information to clients)

"certificates" array, listing all host cert (domain, subdomain, pointer, sub-pointer, or other host, eg:, if present):

			"SSLCertificateFile": "/usr/local/directadmin/data/users/ssltest/domains/",
			"cert_file_host": "",
			"certificate_domains" :
				"Issuer": "C = CA, ST = AB, L = St. Albert, O = Moop, OU = Perp, CN =, emailAddress =",
				"Not After": "Mar  9 01:36:24 2022 GMT",
				"Not Before": "Mar  9 01:36:24 2021 GMT",
				"Subject": "C = CA, ST = AB, L = St. Albert, O = Moop, OU = Perp, CN =, emailAddress =",
				"end": "1646814984",
				"signed": "self-signed",
				"start": "1615278984"
			"valid": "no",
			"warnings": "Could not find 'X509v3 Subject Alternative Name:' in output<br>\n"
			"SSLCertificateFile": "/usr/local/directadmin/data/users/ssltest/domains/",
			"cert_file_host": "",
			"certificate_domains" :
				"Issuer": "C = GB, ST = Greater Manchester, L = Salford, O = Sectigo Limited, CN = Sectigo RSA Domain Validation Secure Server CA",
				"Not After": "Jun 15 23:59:59 2022 GMT",
				"Not Before": "Mar 17 00:00:00 2020 GMT",
				"Subject": "CN = *",
				"end": "1655359199",
				"issuer_simple": "sectigo",
				"signed": "yes",
				"start": "1584424800"
			"valid": "no"

If the certificate is signed, you'll see the signed = yes in the certificate_info. Only signed certificates make the certificate_domains list visible. Use the cert_file_host name for the actual cert filename where the info is from.

The start and end timestamps will be the range for which the certificate is valid.

The issuer_simple will be a "best attempt" for DirectAdmin to set a unified issuer name, which currently include:

  • letsencrypt
  • comodo
  • letsencrypt
  • cpanel
  • sectigo
  • other (for unknown cases)
"next_retries", list of
			"action": "save",
			"admin_ssl": "yes",
			"background": "no",
			"domain": "",
			"encryption": "sha256",
			"keysize": "4096",
			"le_wc_select0": "*",
			"le_wc_select1": "",
			"name": "",
			"next_retry": "1615860053",
			"request": "letsencrypt",
			"start": "1615860053",
			"submit": "Save",
			"type": "create",
			"wildcard": "yes"

Which lists all next_retry files for this User. The main thing to note will be the next_retry time: when the dataskq will next take notice. And the wildcard=yes value, if it's referencing le_wc_select# (for wildcard) or le_select for httpd-based LetsEncrypt requests. The listed variables are essentially what would be "POST"ed to DA normally for a normal LE request, but without affecting the certificate selection area.


All values for this Username are taken from /etc/virtual/snidomains The "snidomains" array has sub-indexes for the host being used. This data will be purely informative, letting the User know which host points to which certificate file. The array index here ( will be the host being requested, while the "cert" value is the certificate host filename.

			"cert": "",
			"user": "ssltest"



Issue a retry on an existing cert (perhaps it is expired or some other issuer)

method: POST
action=certificate  (host certificate file must already exist)
DELETE any existing host certificate file:
method: POST
action=certificate  (host certificate file must already exist)

List of the current next_retries to force to run "now"


Enhanced style skins:


3 new tables within condition:

|*if SERVERCHECKED="checked"|

There is also a new form to force-trigger a retry on the current domain.

method: POST
action=retries   (this can be set to any domain/subdomain/pointer/sub-pointer/ type valid host for this User)
retry_now=<any text>



Absence will set wildcard=yes, subdomains=no, pointers=no. The above 3 optional values only apply to selectX values which are domains.

By default, it's a wildcards, and background dns failure falls-back to http-01 letsencrypt requests for all subdomains automatically.


There are several related commands to help get things synced, if they're ever out of sync or you wish to clean anything up.

echo "action=rewrite&value=snidomains" >> /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq

Which holds the list of all valid certs, who owns them, and all hosts inside. It's used to redirect a requested SSL host to the cert that it lives in. The apache httpd.conf files need this file to be correct for the automatic SSL system to use the best value (domain must use "shared server" or "best match" option in SSL Management page to hunt in this file during the write)

dovecot sni configs

You can rewrite the /etc/dovecot/conf/sni/* files (old are removed first) with:

echo "action=rewrite&value=mail_sni" >> /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq
Force an "Ssl::admin_poll"

This will hunt for all files and process them if the window is correct.

echo "action=ssl&value=admin_ssl" >> /usr/local/directadmin/data/task.queue; /usr/local/directadmin/dataskq
User httpd.conf files

If needed, the User httpd.conf files can be rewritten, using the new snidomains lookup system:

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

This has a task.queue called within the build script, but the full rewrite is recommended for simplicity.


HTTP/2 on 2222: full Go wrapper improved

Major overhaul of the DirectAdmin daemon. It's now using a "Go" wrapper to allow for HTTP/2 on port 2222. This also allows for multiple requests per connection (as it includes HTTP/1.1 as well).

The old directadmin daemon has been refactored to clearly handle this as well, and it listens on a root-only socket at: /usr/local/directadmin/da.sock

which receives the individual requests as it did before. This has a huge performance boost:

  1. HTTP/2 is binary, thus smaller packets
  2. Single connection, saving connection/handshake overhead for each request, making noticeably faster page loads.

The packages will increase in size due to the extra included Go libraries, from a previous size of around 12 Meg (static binaries) now to a new average of around 20 Meg (again, static)

Reseller Create: Can select global IP for new User (SKINS) new

Previously, the creation of a Reseller allowed 3 ip options:

ip=shared		# use the server IP
ip=sharedresller	# use one of the bew shared IPs given to the Reseller
ip=assigned		# set a new IP to be owned by the Reseller

With the introduction of the Global IPs, which are basically a normal shared IP which can span multiple Reseller, this feature gives flexibility in assignments. The Create Reseller page will now include more options, listing each global shared IP on the system, eg: - Shared - Global

where the internal form would be simply: ip=

where would be one of the global IPs. (Cannot currently specify other types of IPs here)


If one of the global IPs is selected, that IP will be used for the User part of the Reseller, If Shared - Server is selected, but Share Server IP is not enabled in the package, the first deteremined global IP from the auto_add_global_ip will be used.

DirectAdmin will attempt to include the selected global IP in the randomized list (where applicable) as part of the count. Eg:


But ip= is passed, only should be in the list, as it would override the "1" item to be used. If it had "2|...", then would be part of that count, even if it's not part of the auto_add_global_ip ip list.


The ip select can be obtained from the existing call to:

method: POST

previously included just:

free_ips = 0
packages = []

but now also include:

ip_select = {}

which uses the standard json selectbox format.



Swap the entire old ip <select> with: |IP_SELECT|

T32165 EVO2064

Location redirects to not include the protocol/host/port : UPDATE WILL LOG YOU OUT improved

With the Go wrapper changes for this release, the c++ DirectAdmin daemon will be accessed through a file socket, not through the ports. The port=2222 or ssl_port=2223 will connect to the Go wrapper. As a result, DirectAdmin doesn't know which port was connected based on the directadmin.conf setting, so it will now rely on the Host header.

  1. The host header includes the port, so any emails from DA (tickets, or user creation) will now reference the host/port that was using when making the request.

  2. All local redirections (mainly for Enhanced actions, eg: email creation) will now redirect relative to / and not relative to the full https://host:2222/ value. This relative redirection is far simpler and makes things like proxies much more compatible (eg: proxying to 2222)

  3. FORCED LOGOUT ON UPDATE: The host value stored in the session will now store the full host header value, so the session will also include the port number. This is a cleaner way of handling redirects, as with the new Go wrapper method which calls the "c" instance with a socket file, the c instance does not know which port the request came in on. Because your old session will the wrong value in it (port is missing), it will fail the referer check so after DA restarts, you'll likely get logged out. This will be normal, just login again, and you'll get a new session in the correct format.


systemd: directadmin.service to run in foreground inside systemd improved

The previous script:

/etc/systemd/system/directadmin.service    MD5: 2ac1c3fa303710d85ba77734c578cff2

started up directadmin as a background service, similar how the init.d scripts work. We've changed the scripts/ to check the md5 of the old directadmin.service, and install a new one. This allows systemd to better manage the service

New cookie flag, enabled by default: SameSite=Lax

current versions of Chrome and Firefox would already set this internally without specifying it, but this would be for:

  • older browsers
  • clarify into it's effect
  • help prevent CSRF attacks for unknown cases

The internal directadmin.conf setting would be: cookie_samesite=Lax

Where you can set it to "Strict" if you wish. Should you need to disable the setting entirely, set it to a empty/blank value, eg:

./directadmin set cookie_samesite ''
service directadmin restart

(value is 2 single quotes)

auto_detect_filesystem_type=1 new

When DirectAdmin starts up, it will now automatically detect if xfs or ext filesystems are in play for each of the quota_partition and ext_quota_partition lists. When setting the system quotas, it will now make use of the fs type for the given partition being set. Reading the usage of a filesystem did already make use of the per-partition format.

The internal default is: auto_detect_filesystem_type=1

where you can disable it, eg:

./directadmin set auto_detect_filesystem_type 0
service directadmin restart

which affects setting the quotas on the xfs or ext4 partitions. (reading usage is not affected, it already checks on the fly)

Note, during startup, if a mis-match is found for quota_partition vs use_xfs_quota, an error.log entry will be made. If it's turned off, xfs_on_domains will also be turned off, and vice versa.

language_list: reduced list of languages new

New directadmin.conf option, internally set to NULL by default (blank). If you wish to reduce the list to say just en and nl, you'd set:

./directadmin set language_list en:nl
service directadmin restart

and for the given list of languges that are actually available, only these languages will be shown. Should the skin not have "nl", for example, even if nl is in languages_list, it would only show "en".

This applies to all skins.

logs_to_keep_days: User log rotation based on time (optional) new

New directadmin.conf option, disabled internally by default: logs_to_keep_days=0

relating to logs_to_keep=5, which counts how many logs are allowed to remain in: /home/user/domains/

the new feature, when set to a positive integer (in days), specifies a secondary log rotation limiter based on age, not just count. This is mainly useful for boxes that do multiple rotations per day, or just want to ensure things in that directory don't get left too long.

It's also useful for subdomain deletion, as those logs are not deleted when the subdomain is deleted, but are also no longer rotated since no new logs are coming in. This would ensure that those possible very old logs get cleaned up at some point.

To enable a setting, say 60 days, type:

/usr/local/directadmin/directadmin set logs_to_keep_days 60
service directadmin restart


DEFAULT CHANGE: pop_disk_usage_dovecot_quota=1 improved

pop_disk_usage_dovecot_quota=0 was the old internal default. Changing the internal default to pop_disk_usage_dovecot_quota=1

This change will use dovecot's quota system to count disk usage quotas, instead of the slower file system traversal.

Should you need to disable it for any reason, type:

cd /usr/local/directadmin
./directadmin set pop_disk_usage_dovecot_quota 0
service directadmin restart

It does assume: add_userdb_quota=1

is already enabled. If it's not, the above setting should have no effect.

Fallback for missing /usr/local/bin/curl to /usr/bin/curl, if exists improved

If the computed internal directadmin.conf "curl" value is blank or missing, and if /usr/bin/curl exists, then:


will be set internally.

Redis: CMD_REDIS (SKINS) new

Read more

one_click_webmail_timeout=10 new

Ability to allow a longer time for the "One Click Login" webmail tool, in case there is two-factor authentication, or the login needs more time.

Internal default: one_click_webmail_timeout=10

to set a higher value, eg 300 seconds, type:

cd /usr/local/directadmin
./directadmin set one_click_webmail_timeout 300
service directadmin restart


Requires roundcube_direct_login-0.6.tar.gz (just re-install RoundCube via custombuild):

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


Default Change for new installs: zstd=1 ipv6=1 improved

Does not affect existing installs, internal default unaffected. For new installs, the directadmin.conf template will have:


Enabling ipv6 addressing, as well as using the zstd backup format for DirectAdmin Backup files. CRON: also swap paths improved

With regards to cronjobs, the script used to only rename the system crontab file. With this change, the contents of the crontab (and directadmin User's crontab.conf), will have its internal paths swapped as well, so crons should continue to run correctly under the new User.


1 1 1 1 * echo "hello!" > /home/OLD/test.txt

will now be swapped to:

1 1 1 1 * echo "hello!" > /home/NEW/test.txt

within the system crontab, and DA's crontab.conf data file.

LetsEncrypt weekly domain rate limits improved

LetsEncrypt has a rate limit of 50 requests per week, per main domain. Read more hereopen in new window

DirectAdmin already had this directadmin.conf option:


but it's now been changed to 200, and will now enforce the limit:


Read more

DEPRECATED: ssl_ignore_when_local improved

The use of a setting to allow non-ssl and ssl on the same port has now been deprecated

DEPRECATED: ssl_ignore_when_local

Instead, setup DirectAdmin to use a 2nd port for ssl:

cd /usr/local/directadmin
./directadmin set port 2223
./directadmin set ssl 0
./directadmin set ssl_port 2222
service directadmin restart

and use 2223 for non-ssl connections, and 2222 for ssl connections.


DEPRECATED: ssl_redirect_host improved

With the change to the new Go wrapper, it's server feature does not support HTTP redirection when listening on an HTTPS connection. The replacement functionality is a javascript redirect using:

<html>use https
<script>location.protocol = "https:";</script>

which will maintain the same host header the browser is currently using.

DNSSEC: newer dnssec-signzone binaries do not support -l lookasidezone (SCRIPTS) fixed

Changed the script: /usr/local/directadmin/scripts/

to check the dnssec-signzone --help output to confirm if the lookasidezone flag exists.

If not, do not include: -l

else it would throw the error:

dnssec-signzone: fatal: -l option (DLV lookaside) is obsolete

If you're on a LAN and use:

./ auto

the will automatically use the task.queue call:


to add the LAN IP to the server/licensed IP. This will setup the LAN IP in /usr/local/directadmin/data/ips/192.168.x.x

The bug is that it's corresponding value in:


was not written.

It appears this bug only affects openlitespeed, where the LAN ip needs to be in the ips.conf and listeners.conf (vs apache which bind to everything and is less picky).


NGINX_REDIRECTS not filled for nginx proxy fixed

When using the custombuild option:


the NGINX_REDIRECTS token is not filled, which would usually be fine, except when a custom token is used to set:


as the token was not present under the HAVE_NGINX_PROXY=1 section in the nginx_server.conf template, so it wouldn't usually be needed, thus unset internally.


DirectAdmin child processes cause temporary cpu usage before processing request fixed

After a full refactoring of the parent/child code, the global mime.types file was read after the fork, before the request is processed.

The /etc/mime.types is quite large, so this was an unnecessary load.

  1. Code optimizations have been applies, and the read of the mime.types file dropped from 399154us, down to 3458us.
  2. The mime.types will be moved to read in the parent, prior to each child fork. This will mean a restart of DA is required if the /etc/mime.types is ever changed.


Allow NULL MX records fixed

You're now allowed to specify: .

as a value for MX records. This means that no email should be delivered to this domain.

php-fpm not being reloaded after restore in some cases fixed

When adding a new User to the system via a restore, if you are running php-fpm, it should be reloaded to create the fpm sock file.

If you had the default: tally_after_restore=1

it would have properly reloaded php-fpm, but only after the post-restore tally finished, which may be a long time after the restore itself actually finished.

The bug is that the reload of httpd is issued to the task.queue immediately after the restore, but one other httpd reload request included affect_php_fpm=no which was incorrectly taking a higher priority. This was not the case for nginx/litespeed, but would affect httpd (even though it would still happen "eventually", it's still a bug). Code changed to correct prioritize the affect_php_fpm=no for any box with any php-fpm installed, regardless of webserver.

T29334 T14469

Improved logic for update.tar.gz download attempts fixed

The purpose of the above is to still have the binaries suspicious of all update IPs, while still allowing the backup flexibility of using DNS to provide new values if the case arises.

When updating DirectAdmin, there is a list of valid IPs it's willing to try. This has now been updated to cross-referenced with a list of live A records to try, so the internal list simply becomes smaller (in case old IPs are offline). Should the last value fail, the list is re-filled purely with the dns list, and the loop is tried over again.

As before, for any control server that works, if the md5 header is passed, but doesn't match the update.tar.gz, that control server can be attempted up to 3 more times as they will return Location header redirects to download from various files servers.


After the pre-release or production binaries with this feature are obtained (available in pre-release Oct 5, 2020), you can manually test the new update process with:

cd /usr/local/directadmin
echo "action=update&value=program&force=yes" >> data/task.queue; ./dataskq d220

to trigger this internal update code, to to confirm it works for you.

If you run into any issues where it should be working (aka: isn't a license issue), please let us know immediately.


This update.tar.gz download code uses the existing logic that the license.key updates already uses, (less the 3x retry per control server), so we're not expecting any issues, but regardless, testing is still important (we've not seen issues during our tests) triggering 3 times during login fixed

The custom script, if exists:


was being triggered up to 3 times for 1 session based login as there was no check for existing file prior to the trigger.


Only call it if the file does not yet exist.


Segfault during json read fixed

The code to read a json file directly into a ConfigFile class is used in a a few places, but it was discovered when reading:


which may apply when trying to save custom php-fpm configs when the User httpd.conf was written.

swap da_sso definers for triggers, events, routines, views fixed

Related the previous change for mysql.proc defined here

where creating triggers, events, routines, views, etc as a temporary phpMyAdmin one-click "SSO" account (which eventually gets deleted) causes issues with those entries, thus need to be swapped out to username@localhost.

this extends that change to work on the 4 tables:


but using the proper "ALTER" commands for each one. The triggers and routines will do a drop and re-creation as they cannot be "altered' in a simple manner.


cd /usr/local/directadmin
echo "action=delete&value=pma_sso" >> data/task.queue.cb; ./dataskq d306

which will always swap the definers, even if the da_sso_%@localhost account has not yet expired and has not been deleted.


Restore: random shared: That IP does not exist fixed

Restoring an account if "Random Shared IP" is selected resturns "That IP does not exist". Swapped of ip=<special value> to ip= was not being done.


./directadmin --DocumentRoot as User could not gain root fixed

Relating to the call to:

/usr/local/directadmin/directadmin --DocumentRoot

while running with directadmin in suid mode (root:directadmin/4755) broke a few versions ago. Re-organized the proper suid methods to re-gain root from a User.

Unicode 4 byte chars needed conversion fixed

When encoding long 4-byte character in json, they needed to be converted from u32 to u16 before encoding.

UTF-8 T29832

IDN: convert_to_punycode=1 attempt to convert iso-8859-1 to utf8 internally fixed

The Evolution skin will automatically convert a newly created domain to punycode piror to submitting it to DA. If you're using Enhanced skin with iso-8859-1 charset: eg, you've not run this

OR you're using WHMCS which is not passing a UTF-8 encoded string

then in order for the convert_to_punycode=1 auto-encoding to work, it must be first be converted from iso-8859-1 to UTF-8 internally for the punycode conversion to work. This fix adds this convertor to the punycode encoder if chunk encoding fails (it will attempt the 8859-to-utf8 conversion and try the encoding again). This is really only meant as a fallback, as it's far safer to pass things either in UTF-8 in the first place, or even better in punycode, however we understand that this is not always the case.

Note that convert_to_punycode=1 is NOT enabled by default. This change only happens if you do have it enabled by default.


This change has been removed as of DirectAdmin 1.63.3

Relating to the new hotlink feature defined here

This fix will auto-fill the http + https x + * x all pointers, into the list of valid referer URLs. You can delete URLs after that, but if you delete everything, it will re-add the default list again.

tally_after_restore=2 to push restarts to task.queue.da, tally to task.queue fixed

The purpose of tally_after_restore=2 is to run the tally in a 2nd dataskq process later on, so as to allow the results to be messaged to the creator more quickly. This works fine, however another reasont to use tally_after_restore=2 would be to have the restarts happen more quickly. The issue was both the webserver restarts and the tally are then in the same task.queue, and DA always puts the restarts at a lower priority. This means the services still won't be restarted until after the tally finishes.

Fix: When tally_after_restore=2 is enabled, the service restarts are now pushed to the task.queue.da file, instead of the task.queue file. The tally is pushed to the task.queue, to be run in the next minute. The task.queue.da file is always run after the task.queue, thus solving the race condition.

Note that the task.queue.da's tally request will be processed in the SAME original dataskq call becaues the task.queue.da is called next. This means that your service restarts will no longer wait for up to 60 seconds after a restore. They'll happen immediately in the same dataskq instance as the restore, making your website live/updated at the same time that you get the finished notice (without waiting for the tally)

This means the the instant service restart applies to ALL states of tally_after_restore=* (0,1,2), they're restarted instantly.

tally_after_restore=1 does the tally before the instant restores.
tally_after_restore=0, no tally, instant service restart
tally_after_restore=2, tally run in next dataskq instance, restarts instant in same instance.


count_other_disk_usage: can have multiple instances fixed

When using count_other_disk_usage=1, there is a custom script which can be used to add extra usage to a User's quota total defined here

However, with the introduction of plugins/hooks to allow their own instance of the custom scripts defined here;

This means that the script could exist more than once. Although each one would be correctly called, their results would overwrite the previous for each call.


Each script now has it's own unique return descriptor, and they're assembled/added after all instances are finished.


If you need more info about which script is returning which value, use the tally debug level 432, eg:

cd /usr/local/directadmin
echo "action=tally&value=fred&type=user" >> data/task.queue; ./dataskq d432

and look for lines starting with:



CMD_API_LOGIN_KEYS added to core_functions feature set fixed

The "core functions" feature set had CMD_LOGIN_KEYS, but this change also adds CMD_API_LOGIN_KEYS.

CMD_DNS_CONTROL: User Level DNSSEC: reload named after singing fixed

For the User-controlled case where a domain is signed with DNSSEC, named was not being reloaded.


Google Cloud MySQL 5.6 to revert to SET PASSWORD instead of ALTER USER, and mysql_use_new_user_methods=1 fixed

Only affects:

mysql> SELECT version();
| version()         |
| 5.6.47-google-log |

which is MySQl 5.6 on a Google Cloud setup.

DA would have been using the ALTER USER syntax to update the password for a Database User. The Google could MySQL 5.6 does not like this, so DA will use SET PASSWORD for all MySQL 5.6 installs. All other versions are unaffected.

Also, if google-log is at the end, it will internally set: mysql_use_new_user_methods=1

allowing all new methods to be used.

Other MySQL 5.6 boxes (eg: local installs) are not affected.


search other paths for favicon.ico fixed

Previously, the favicon.ico was search for in the "images" folder of a skin, eg: ./data/skins/evolution/images/favicon.ico

If that path does not exist, it will now fallback to hunting in the top-level of the skin, eg: ./data/skins/evolution/favicon.ico


tokenize_script_output: not setting tokens fixed

Bug introduced into the code July 27th, preventing the use of: tokenize_script_output=1

where a |CUSTOM| token set to something like this:

echo "|";
echo "?T=goodbye|\n";
#T is |T|

Would have generated the output:

#T is hello

Where it should not have output the |?T=goodbye| part, but instead should have output only:

#T is goodbye

Delete database: use REVOKE fixed

For the internal mysql_use_new_user_methods connections, for each User on the database in the mysql.db table, instead of the previous "mass" clearing method which used a direct call to mysql.db, it's been changed to use the existing function call to remove single Users, for each Uesr (including the system account). This is also a cleaner method to clear existing access hosts. It will also now trigger, where it not do this previously for DB removal.


BFM: being cleared fixed

The Brute Force Monitor will track both the number of attempts from a specific IP, but also the number of attempts on a specific User (from any IP) This can be handy to deduce if a distributed attack is being executed on a specific account from multiple sources.

The bug would write the data/admin/ as an empty file if no new usernames come in to block, but there are IP based attacks (eg wordpress attack) as the user list wasn't loaded for efficiency, but was written in the empty state.


Track lockfiles and clear on SIGTERM fixed

The dataskq did not previously handle the SIGTERM signal as it was not a daemon. However, there are cases when it might exit prematurely (Eg: server reboot) where a SIGTERM is sent to it. The issue was that any lock files created were not being cleaned up. Fix

  • added a lock file tracker (added to list when lock is created, removed from list when unlocked).
  • signal handler for SIGTERM in the dataskq to call a cleanup of any leftover items in this list.

Cleanup calls also added to directadmin processes, in case they've missed their unlock state (not aware of issues there, but backup check is low cost to add). Should any cleanup happen, you'll see this in your system.log and (error.log or errortaskq.log): sys::remove_tracked_temporary_files: removed temporary file '%s' where %s is replaced with the file that was removed.

The tracker also know which uid/gid the locks were created as, thus they'll be removed with the same access level.

Prevent IP deletion if it current has linked IPs fixed

For any IP being deleted, if it has linked IPs, those linked IPs should first be unlinked prior to the deletion of their parent IP. This check simply blocks the deletion of the parent, if it still has linked values on in.

BFM: Huge speed improvement with json fixed

The Brute Force Monitor contains several tables which can be searched/sorted. When converting these tables into json for the Evo skin, optimizations have been found allowing the conversion to happen much more quickly. On a /root/blocked_ips.txt file with 45,000 lines, previously taking about 2 minutes, now has time to first byte (TTFB) in 1.7 seconds.

Reports of segfaults upon BFM timeout (back-end segfault, User doesn't see it). However, our current code-base (Go rewrite) does not generate the segfault as it's entirely different, so simply updating should resolve that issue, should you be affected. But the major speed improvement, above, also greatly lowers the chance of segfault anyway, so win-win.


Domain Create: system_user_to_virtual_passwd fixed

For existing Users, add the system account to /etc/virtual/ when additional domains are created.

Decrypt backup on OpenSSL 1.1.0 fixed

The and use the internal default digest for the password key. As of OpenSSL 1.1.0, the default md has changed from md5 to sha256. The .sh script did not specify which digest should be used, so decrypting a tar.gz.enc file on a newer OS (CentOS 8), where the file was encoded on an older OS (CentOS 6), would result in a decryption error:

bad decrypt
140209317685056:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:crypto/evp/evp_enc.c:616:

The solution is to change both scripts to explicitly use -md sha-256. However, for .enc files that have been created with md5, should the first decryption fail, it will automatically attempt an md5 decryption.


The warning:

*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

can be ignored for the time being and does not currently affect the actual decryption. This will likely be reassessed in the future to determine if those options can be easily added to all encryptions, where available, without affecting decryption with various openssl versions.


Creator user.conf check_subdomain_owner override for other User domains fixed

The check_subdomain_owner user.conf override for a Reseller creating a User, was not correctly respected:


  • global: check_subdomain_owner=1
  • Reseller "res" has check_subdomain_owner=0 in their user.conf, allowing creation of any sub-domain.
  • User "fred" already exists under some OTHER creator (not created by "res") with
  • Reseller "res" creates User "subfred" with
  1. The realtime form/json/ajax check passes, as it's checking if "res" could create the under "res" User account (not entirely correct, but accurate for what we need). This is fine.
  2. During the creation of User "subfred", was added to the domains.list of subfred prior to the attempted and denied creation of domain "".
  3. The creation of was denied as the check was then done as User "subfred", without any user.conf override, thus blocked (Reseller's user.conf was not checked)

This caused a broken state for subfred (User deletion does clear it out correctly), but the expected behavior would be to respect the the user.conf of "res" at subfred creation time. Fix moves the subdomain owner check before any part of the domain is created, and also includes the reseller's user.conf for the user creation state.

This only applies to User creation. A domain being created by the already existing User would not check for their creator's user.conf override, as the creator is not the one creating the domain in this instance.


rewrite httpd.conf to rewrite php PATH in cron/.bash_profile fixed

If the CustomBuild options.conf php1_version is changed, DA itself doesn't know about the change being triggered. The result is the cron PATH and ~/.bash_profile files may have a PATH=/usr/local/php72/bin path set, when (for example) php1_ver=7.4 was set. The ./build rewrite_confs does issue a full User httpd.conf rewrite, so for any task.queue trigger of an apache rewrite (action=rewrite&value=httpd), this will trigger the rewrite of the cron and .bash_profile, resetting the correct php version, when needed.

Note, the ./build php call does issue a full rewrite, so this would also trigger these file. The PATH is only set to a specific /usr/local/phpXX/bin path when a given domain is not using the default php vesrion (php1_ver). If the domain uses php2_ver (for example), that's when the PATH specifies something different for the cli calls to "php". The default php version would use /usr/local/bin/php, which will be a symbolic link to the active php1_ver, eg: /usr/local/bin/php -> /usr/local/php70/bin/php70 hence no PATH is needed to access it (aside from /usr/local/bin) This link may also exist, allowing just /bin to work to: /bin/php -> /usr/local/bin/php

FileManager: nb-space vs space (SKINS) fixed

html output encoding was not encoding files saved with an nb-space when displaying. Result was form valued for edit/copy had wrong "old" value, thus rename failed. Anything with nb-space (&nbsp) will now correctly display it's encoded value.



changed the name="old" from:





BFM: mod_security scans not triggering IP block (SECURITY) fixed

If you've enabled the brute_force_scan_mod_security_logs=1 to block IPs that have too many failed mod_security connections (it's off by default), the feature was logging the entries, but not adding to the count for the block decision. An entry was being made into the file, but it was not specifying which service was being blocked (json formatted checks had the bug, only mod_securiity at this time) Fix properly adds the mod_security1=# / mod_security2=# to the data/admin/ file.

task.queue: rewrite mail_sni removing pointers fixed

Bug with the rewrite of the /etc/virtual/snidomains file where the values from /etc/virtual/domainowners are used, and pointers should not be used during the rewrite. Pointer would be a subset of the User domains (pointer values are within the domain's cert), so only domains should be considered. Fix: ensure data/users/USER/domains/DOMAIN.COM.conf exists in the domainowners loop, when doing the rewrite.

Related errors:

2021:01:16-09:07:04: Ssl::write_mail_sni: unable to read ./data/users/USER/domains/POINTER.COM.conf to determine cert/key: Unable to open ./data/users/USER/domains/POINTER.COM.conf for reading.<br>


Majorodomo: Can't locate in @INC (TEMPLATES) fixed

Newer versions of perl do not include the current working directory in the @INC list. This is a related error:

Can't locate in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5) at /etc/virtual/majordomo/config-test line 129.

Note how the @INC list does not end with . after /usr/share/perl5

You can confirm your list with this command:

perl -e "print qq(@INC)"

if the very last character of output does not show ".", then this fix will be needed.

Here's an older box with the trailling, which does not need the fix;

[root@es6-64 directadmin]# perl -e "print qq(@INC)"
/usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .


The fix was to add:

use lib '.';

to the top of: /usr/local/directadmin/data/templates/

and simply issue an edit/save to the mailing list, which will update: /etc/virtual/

with the new @INC list, including the trailing dot ("current path").

Another solution (not implemented) was to link the non-found files to the /usr/local/share/perl5 folder:

cd /usr/local/share/perl5/
ln -s /etc/virtual/majordomo/
ln -s /etc/virtual/majordomo/
ln -s /etc/virtual/majordomo/

which also appears to work, but is less preferable as it affects all perl scripts on the system, which might end up using a majordomo .pl file if they happen to use the same name.


systemd: proftpd: use reload, not reread fixed

The older init.d/proftpd supports "reread", but systemd does not. If systemd, use reload when reread is requested (swapped just before task.queue is written)


E-Mail limit headers: per_email_limit_message.txt affecting per_email_limit_email_message.txt fixed

When a per-email limit is hit, notices are geneated.

  1. The Message System will get a notice using template: per_email_limit_message.txt when notify_on_mass_emailing=1

  2. the email would receive an email using template: per_email_limit_email_message.txt when notify_email_on_per_email_limit=1

The same set of "tokens" was passed to both templates. The bug was that if per_email_limit_message.txt set some header/value, that value would carry over to the per_email_limit_email_message.txt template.

A specific issue was the |?HEADER=| was being set in 1, causing Conent-Type <html> not to be set correctly in 2.

Fix: Use unique tokens container for each template call. First will no longer affect the second.


FileManager: Only show protection enabled if htpasswd matches fixed

When viewing the password protection status of a directory, previously, only the presence of: required valid-user

was needed in the .htaccess file. With imports of websites from other servers, this needs to be more specific.

This change expands the checks to also validate the line:

AuthUserFile "/home/admin/domains/"

where that path must match the path of the actual .htpasswd file that DA is working with. If there is no match, the checkbox will not be checked.


If the checkbox is not being checked when you think it should be, run DA in debug mode, level 10 is sufficient, and check for one of these 2 messages:

"FileManager::protection_enabled: FALSE because %s does not start with %s"
"FileManager::protection_enabled: FALSE because %s != AuthUserFile %s"

which should tell you which one is the cause.


Email::purge_spam: '.Deleted Messages' is not a valid folder name fixed

The nightly purge did not work on the "Deleted Messages" folder due to spaces not being allowed in email folder names. Spaces are now allowed.


Apache owned files: non-/domains directory fixed

The apache owned files were only counted under ~/domains/* This includes other relevant files and folders under ~/ to be included in the apache count.


dovecot_extra_fields: looped append in value fixed

If the dovecot_extra_fields value was set in the Domain's file, and dovecot_proxy=0 was used, The call to:

echo "action=rewrite&value=email_passwd" >> /usr/local/directadmin/data/task.queue;

would end up triggering a longer and longer line for each updated entry in the file /etc/virtual/


Restore: Use Local NS ambiguous unset state fixed

The local_ns=yes|no setting is a newer one (added after backup system was created), where it's possible this value was not set in the data/admin/backup.conf. By default, the back-end would have used the NS values from the backup for restore if this was unset. However, the confusion was that the GUI would show the proper default of local_ns=yes when unset, which was a mismatch.

Clarified things in the back-end and always sets/resturns the local_ns value, with the default value being yes/true for using the Local NS records by default if unset.

Secondary bug where, during a restore, if any NS record values happen to be for this zone, but there are not any A/AAAA/CNAME records for them, DA will add them accordingly. This typically implies a broken zone state, or the confusing with the above where the local NS values (set in Reseller Level -> Namservers) were used and match this zone, but the "A" restore being used for the NS record simply did not exist. DA will now check all NS records, regardless of local restored values or not, to ensure that if they are part of this zone, they will resolve by adding a matching A/AAAA as needed.


FileManager: Edit UTF-8 files fixed

The "TEXT" token for the edit textarea previous did not encode multi-byte characters properly. This change allows utf8=yes to be added to the GET request, eg: CMD_FILE_MANAGER?path=/TEST/test.txt&action=edit&load_token=TEXT&utf8=yes

such that the file-read will notice if the current byte is >=0x80, peeking 3 more bytes ahead to get the possibly full utf-8 character. This allows DA to sort out the unicode number and properly generate an html decimal encoding. If it ends up not being a multi-byte char, then it rewinds those few bytes and continues encoding this char the old way.

The Enhanced skin does not pass this value as it's iso-8859-1. Using the Enhanced skin to show and edit UTF-8 files would likely garble the data. If you need to edit UTF-8 file, either swap Enhanced to use UTF-8 (see below), or use the Evolution skin which is only UTF-8.

EVO2051 T31739

Suspension: clear shell from /etc/passwd to block login keys (SECURITY) fixed

Previously, for ssh access, the user suspension would only lock the account with usermod so that the crypted password has a ! character in front. This was fine, however it did not account for login keys which never check the password field. Solution is to also swap the shell to /bin/false (or /sbin/nologin) when the account is suspended.

DNS: Adding long wrapped TXT records ignores TTL fixed

The TTL value was being ignored when setting a long/wrapped TXT record. The value was swapped to the long type prior to the internal name+value TTL lookup, which no longer matched, so the default was incorrectly used instead.


Ssl: key validation: use -module, not -check fixed

DirectAdmin was validating ssl keys using:

/usr/bin/openssl rsa -check -noout < key

It was found that on CentOS 7, some cases did not return 0, even though the key was valid:

RSA key ok
140085668738960:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:tasn_dec.c:1239:
140085668738960:error:0D06C03A:asn1 encoding routines:ASN1_D2I_EX_PRIMITIVE:nested asn1 error:tasn_dec.c:807:
140085668738960:error:0D08303A:asn1 encoding routines:ASN1_TEMPLATE_NOEXP_D2I:nested asn1 error:tasn_dec.c:739:Field=n, Type=RSA
140085668738960:error:04093004:rsa routines:OLD_RSA_PRIV_DECODE:RSA lib:rsa_ameth.c:121:
# echo $?

We've changed the validation to simply get the modulus:

/usr/bin/openssl rsa -modulus -noout < key

which did return a zero exit code.

Cannot Execute Your Request
Key is Invalid


Delete Access Host: REVOKE GRANT OPTION if grant_priv=Y fixed

The REVOKE ALL PRIVILEGES command was not sufficient to clear an access host if grant_priv=Y was enabled for this DB User. If grant_priv=Y on this db+host, then also call the REVOKE GRANT OPTION command. If there is an error in either query, the old DELETE FROM mysql.db query is run (only as a fallback)


restore or creation with passwd_is_crypted chpasswd double input fixed

Related to very old bugfix defined here

which would pipe the crypted uesr:passwd to chpasswd, the bug was that the fix piped using the system < pipe method, but the stdin method (directadmin to chpasswd) was still being used. As the change was for CentOS 5, this change will now be reverted to only use the inter-process pipes, and will no longer use the disk write before a < pipe.


The per-domain quota output in the json=yes format was previously:

			"quota": "653.505",

this was a bug in that:

  1. It did not respect the bytes=yes column
  2. It did not show the quota limit

The output would be:

				"limit": "shared",
				"usage": "685249658"


This is a behavior change and is not backwards compatible without a change to the call. If you were calling, eg:


the output is now different (unrelated to bytes=yes) You can revert to the old output method by adding:


which shows the old string instead of the array. We recommend converting to use the new array method instead as "old_quota_format=yes" may be eventually deprecated.


directadmin.conf option: simple_disk_usage=0

would hide the output entirely, and completely relies on system quotas for disk usage. The tallies use far less disk I/O, but per-domain usage is not counted (no directory traversal)

EVO 2126

dataskq: affect_php_fpm: only for webservers fixed

The internal affect_php_fpm flag would automatically duplicate the action call to php-fpm if apache is restarted. But was it was doing it for all services, causing php-fpm where they were not needed. Fix: only apply this to web servers (httpd,nginx,litespeed,openlitespeed) Only applies if php-fpm is enabled in CustomBuild's options.conf

Last Updated: