Version 1.53.0

Released: 2018-03-14

More JSON new

More json for the new skin.



Array of line numbers for index, value sub-array with:

type: header|unchanged|plus|minus
line: data for this line

also in the main array:

lines: <number of lines>




CMD_TWOSTEP_AUTH - page to setup and modify 2FA.

CMD_ASK_TWOSTEP_AUTH - page to enter code.

You might get a dynamic error on login with code:

"error": "Two-Step auth failure",
"result": "auth required"

letting you know to ask for the code, and enter it.

Then return codes for entering it should be self explanitor.. errors=bad, success=good you're in.


"succes": "Two-Step auth success",
"result": ""

Note that a few of the error conditions imply the session has been destroyed, so don't keep trying. Full redirect to main screen needed (login page)

"error": "Two-Step auth failure"

with the "result" having a few versions:

"session destroyed on verify"  - don't bother trying again. Full re-login required.
"attempt logged" - failure, but can try again.
"session destroyed on ask" - don't bother trying again. Full re-login required.




non-standard json, test extensively.

May need a directadmin.conf options to override the paths:



to a skin folder, eg:



so that the skin can control the display of that page, rather than the template directory(similar to login.html features.php?id=2009)



for both loading the security question page, and submitting the answers.

Standard success/error output, except the actual page load which should have:


and sometimes:


if a wrong answer is given an the question page is show again.



and use this to create your own lost_password.html in the skin's directory:

CMD_LOST_PASSWORD lost_password.html from skin's document root add old filesX new

As some were using to pre-scan uploaded files, the newer file[] method only showed a single file, when multiple might have been uploaded.

This change will include the old files1, files1, files3 values to the script, in addition to last file[] (which isn't of much use if multiple files are uploaded), and will also set:


so you'll know the last "filesX" value and "file" will be the same.

Note, that you can alternatively use the, which is going to be called for each uploaded file (multile files = multiple calls)

show_all_users.cache now contains User "package" new

The cache file located at:


will now contain the user.conf package value for all Users/Resellers.

Note, that it's the User package... so although you might have assigned a Reseller some specific User package, they do have the ability to set their own User account to a different value (one of their own User packages)

Just be aware of that, where you might be expecting to always see a Reseller package if it's a Reseller account, but it could show their own User package if they changed it.

In the event that a User has been customized (manual setting of some User setting), it will show:


If that is the case, then you should also see:


so you know what they used to have.

CMD_LOST_PASSWORD lost_password.html from skin's document root new

Previously, the lost_password.html for CMD_LOST_PASSWORD was only available from the templates directory.

This change will allow your skin to place the file at:


and DirectAdmin will look there based on the directadmin.conf docsroot setting:


Read priority:

  1. /usr/local/directadmin/data/skins/YOURSKIN/lost_password.html

  2. /usr/local/directadmin/data/templates/custom/lost_password.html

  3. /usr/local/directadmin/data/templates/lost_password.html

Include iotop output in load monitor new

The check_load option previously sent a notice to all Admins including the "top" output for that moment.

This change is to also include the iotop output, if it's available.

CentOS 5:

load_iotop_string=/usr/bin/iotop -b -n 1

CentOS 6/7 + Debian

load_iotop_string=/usr/sbin/iotop -b -n 1


load_iotop_string=/usr/bin/top -b -d 1 -m io all

where you might notice "top" is used with the "-m io" option, to give io output, instead of cpu output on FreeBSD.

In order to use this feature, you must ensure the iotop binary exist in the above location for your OS.

To disable it, set a blank value in the directadmin.conf:


DA will check to see if the binary exists before running it (string up until the first space), so if the binary doesn't exist, that's fine too (the setting won't hurt anything)

general environmental variables: login-as and login-keys new

Things like plugins, template scripts, and some scripts will load in the general environmental variables.

This list now has a few extra items:

LOGIN_AS_MASTER=creatorname#this variable is only set if IS_LOGIN_AS=1
LOGIN_KEY_NAME=keyname#this variable is only set if IS_LOGIN_KEY=1

Also added to the and scripts.

Also added:


to the global environmental variable loader (not in nor, but in other things like plugins)

listen_backlog to adjust the listen() backlog size new

The previous hardcoded backlog size was 8.

This is now a variable, with the internal default:


to change the number, add it to the directadmin.conf, and restart DA, eg:


Related error on FreeBSD:

sonewconn: pcb 0xfffff80132ddbc40: Listen queue overflow on port 2222: 13
already in queue awaiting acceptance (142 occurrences)

Ability to override default /home directory (SKINS)(LANG) new

Relating to the /etc/defaults/useradd HOME default method, this method lets you just add:


to the directadmin.conf, and DA will explicitly set this value when creating the User.

This will override the useradd internal default and /etc/default/useradd HOME default.

Applies to any OS.

You can now also specify a desired /home directory, settable in the skins, if you add something like:


where all paths must exist before DA is restarted, else none will be set.

Once set, the package will be able to have, eg:


allowing that account to be created into that path.

Note: Since there are no Admin packages, the directadmin.conf method is the only way to alter the admin home directory.

(but you can post the desired create_user_home_override=/home2 with the creation, which would be accepted even though it's not in the form)

At this time, changing the create_user_home_override value in a package will not move a User to a different home directory.

Same for editing a User's settings.. the user cannot be moved to a /home2 (for example) through DA.


both User and Reseller packages will now support the setting:


where the value must exist in the directadmin.conf setting for home_override_list or will be ignored.


If specified, this value will be set in the user.conf during account creation.

So DA will attempt to restore it to this same path.

However, you must have already setup the home_override_list in the directadmin.conf, or the value will be ignored.




<tr><td class=list>|LANG_HOME_DIR|</td><td class=list align=center>|CREATE_USER_HOME_OVERRIDE|</td><td class=list>|LANG_LOCATION_FOR_THIS_ACCOUNT|</td></tr>


will show the current /home2 path if home_override_list is set (/etc/passwd)

BUT if the user.conf create_user_home_override does not match the current home path from /etc/passwd, then a warning is shown in column 3:

"Does not match setting: /home"



LANG_HOME_DIR=Home Directory
LANG_LOCATION_FOR_THIS_ACCOUNT=Location for this account


593=Home path '%s' is not in the allowed home_override_list
594=Home Directory
595=Does not match setting: %s


140=Tried to restore '%s' to '%s', but this path is not in the %s
141=Account created in directory '%s'

/config.json for custom skin variables new

Relating to the custom skin logo and custom css colors features (related links below),

this lets the skin owner save raw json data to a file at:




Both Resellers and Admins can set modifications on global skins, or their own skins in /home/creator/skins/*, but the config.json is saved in their skin_customizations path.



method: POST  (must be post)

Only very basic json validation is done on the uploaded data, likely to have better validation at a later date.


to delete the config.json, run the same as above, less config.json, but also include:



method: POST  (must be post)

To access the file, use the path:


It's considered a "non-threat" path, so is similar to IMG or /assets requests (tokens not loaded, etc)

New global token:


will be set if config_json=1 is in the skin.conf.


to enable this feature, the skin's skin.conf must have:


else the save will not be allowed.

The files_user.conf is currently needed to allow access to the file.

Notify if login-as account is not using a global skin (SKINS)(LANG)(SECURITY) new

Sometimes you might not want to login to an a User or Reseller account, using a skin a Reseller or other Admin have uploaded.

This feature will add a notice if the skin isn't global, shown on CMD_SHOW_RESELLER and CMD_SHOW_USER pages below the "Login As" button.

You can also set it such that if you do decide to login anyway, it will override and use a global skin.

Internal default is:


which gives a notice, but allows the login using the Reseller skin in /home/reseller/skins/skinname

If you want to only ever login-as with global skins, set it to 0 by adding it to the directadmin.conf, and restarting DA:


Note, you can fully disable the feature by setting it to 2:


Where no notices are added, and full login is still allowed using the Reseller skin.

If DA has trouble figuring out if a docsroot is global, or who it belongs to, you'll get an error in the:


starting with:


and the error following after that on the same line.






added this, but you can use some other file, or directly insert the code into the 2 above show_*.html pages, however you want:


new file:




which make use of the tokens below, so should only be called via the 2 commands below.




IS_RESELLER_SKIN=-# (same as json below)
if IS_RESELLER_SKIN==-1 then
if IS_RESELLER_SKIN==1|2 then
use_current_skin=yes will be passed in the form, so the session stores a skin path override.

IS_RESELLER_SKIN_ERROR: string starting with "error: " containing an error message why DA couldn't figure out the owner of the skin.

RESELLER_SKIN_OWNER: the owner of the skin, only set if 1 or 2 above




is_reseller_skin=0    - it's a global skin no worries.
is_reseller_skin=1    - it's a reseller skin, but you'll be logging into it. Check variable reseller_skin_owner
is_reseller_skin=2    - it's a reseller skin, and you'll be swapping to a global skin when you login. Check variable reseller_skin_owner (if allow_admin_login_as_to_reseller_skin=0)
is_reseller_skin=-1  - an error occurred  Check variable "is_reseller_skin_error"

reseller_skin=/home/reseller/skins/resellerskin OR more likely:

Changed user.conf and reseller.conf "sentwarning=yes" to "sentwarning_bandwidth", and quota/inode (IMPORTANT) new

The user.conf and reseller.conf files had a variable:


which was used to determine if an overusage notice was sent or not.

This issue with that was that it tracked all 3 overusage types:

bandwidth, quota, inode

so if a User went over quota first, they wouldn't get a notice if they went over bandwidth later in the month (for example)

The "sentwarning" variable is now deprecated and replaced with 3 new values:


For most people, this won't matter much, Users will just get multiple notices if they go over limit with multiple types.

However, some people may have scripts that manually change this value for various reasons.

This variable will need to be swapped to the 3 new values, as needed in any custom scripts that use it.

Update: After coding it, I realized the Reseller notices were also using the user.conf sentnotice.

So now a Reseller went from a possible only 1 notice, now to 6 possible notices, as reseller.conf does track it's own notices now.

/home/reseller/skins to save docsroot as absolute path new

Previously, when saving the docsroot value in the user.conf, it would be relative to /usr/local/directadmin, eg:


This shouldn't be required, and just makes it messy, so future skin changes for Users will save the absolute path:


/assets to use 1/10 the cache_time new

The default value of:


will have any requests for /assets divided by 10.

For example, a request to:


will have:

Cache-Control: max-age=360

After expiry, the E-Tag check should still allow the local cache to be used, saving another download.

But as DA uses 1 request per child, and does not allow multiple requests per connection, we still want some cache to keep the re-connects to a minimum to maintain page load speed

(Etags checks will still cause a full connection/request, even though the reply is small, the overhead of the connection itself is the bottleneck)

Clustering to show accounts/domains/info from remote servers new

The Multi-Server Setup (MSS) was original designed to link many servers together.We've already got options like dns zone syncing, checking remote servers for Users/Domains, and email account pushes.This addition will pull the info from the remote CMD_API_ALL_USER_USAGE call, and merge it into the local CMD_ALL_USER_SHOW output.

When enabled for a remote MSS server, the local CMD_ALL_USER_SHOW table will include a new "Server" column.When you click on that User/Reseller, it will redirect you to that remote server, where you'll need to login.

This is currently just informational, so you cannot delete or manage a remote Users locally.But you can click on the User url, which will redirect you to the remote box, but you must login to pass the referer check.



new token will be filled with |RESULT| if there are errors with the Cluster connection or authentication.

Added a .warning_info css entry in the style.css.


When calling CMD_ALL_USER_SHOW?json=yes

check for the "remote_server" column.

it will be set to an IP or hostname.

If it's set (not blank), then this is a remote account, so the URL must be treated differently.

To assemble the URL, there is an array called, eg:

                "port": "2222",
                "ssl": "no"

which you can use, to do a lookup on that remote_server IP, eg:

"remote_server": ""

which you can then do a lookup i the remote_server array, to determine which port and http or https with ssl=yes|no.

ERROR will be included in the json output in the top level json array, eg:

"RESULT": "Error with authentication on<br>\",

Save Certificate request info for future requests (SKINS) new

New file:


which will store the fields from the certificate requests:

  • self-signed

  • certificate-request

  • LetsEncrypt request

anything entered, will be saved for future use, auto-filled in the form.



add the format:


values for all respective input fields:






Separate brutecount into brutecount and brute_dos_count (SKINS)(LANG) new

The DirectAdmin Brute Force Login detection on port 2222 at:

Admin Settings -> Blacklist IPs for excessive DA login attempts: [x] after [100] attempts

previously tracked all connections to DA that were not authenticated.

This was confusing, as it wasn't an actual login attempt, but things like loadig the login page counted against this max before ban.

To simplify things, this value is now literally only for actual failed login attempts (eg: wrong user pass)

The new default for new installs will be:


down from 100.

To address flooding DA, we've added another setting:


which tracks all unauthorized connections.

If you load the login page that many times, then you'll be banned.

The time window for which the attempts (either failed logins or unauthorized connections) must pass with no activity before the count is reset, both still use:


which is the internal default, and can be changed.

There are some cases where browsers decide to keep making requests, for things like robots.txt, for no particular reason (reported with firefox).

So if you leave your window open/idle, or even sitting on the login page, long enough, these requests will get your IP banned, even if the rate is slow.

This change will allow you to set a lower failed login attempts limit, and higher idle requests limit, so you can have the best of both.

The brute_dos_count is still going to be the defence against dos/flood attacks, so if they are flooding you, hitting 100 would happen fairly quickly anyway, so setting this value high is fine.


The previous tracking used to be done in the file;


storing all IPs.

This wasn't the best design as it would be blocking or hitting race conditions for the counts.

This new change will use the folder:


for each IP, eg:


where the above 2 files will store 1 byte per attempt, so the size of those files would be the number of attempts.

This makes tracking much quicker, and non-racing, in that is just attempts a "1" to the end of each file for the given case.

Once login is successful the failed_logins is counted, and added to the Users logins.list, as before, and the directory is removed.

The dataskq will also still check the logins as before, but instead of admin/login.hist, it checks admin/ip_access, and clears and folders that are either empty, or who's 2 files mtime's are older than:

now - mtime > brute_force_time_limit

if the files don't exist, the mtime will return 0 anyway, so it would be true, and removed (both files must be true for removal of the folder).


Admin Settings:


add an input for brute_dos_count below brutecount




LANG_UNAUTH_CONN=unauthorized connections.


Default change: unblock_brute_ip_time=1440 clear_blacklist_ip_time=1440 bruteforce=1 new

Previous default for unblocking an IP from the Brute Force Monitor was 0, never unblock.

This would cause many IPs to fill up the iptables without ever being unblocked.

New default is:


in minutes, which is 1 day / 24 hours.

You can still adjust the value as needed.

Existing installs that have made a change to this value in the Admin Settings won't be affected, you value would remain.

If you have saved 0, then this value will also remain.

So it's likely only going to affect new installs, unless no directadmin.conf have have been made from Admin Settings.

Also changed defaults for 2222 blocking from:




to automatically block attacks on the 2222 login.

CMD_DNS_MX to allow login-as editing, even without User privilege (SKINS) new

Similar to:

Ability to edit dns zone through Login-As even if User doesn't have access (SKINS)

Feature now applies to CMD_DNS_MX.


Same skin changes as id=1631, but in:


paniclog check every minute new


After some testing, it was decided to set the default value to 0, so it's disabled by default:


You can enable the feature by setting it to:


The dataskq will now check for a non-zero paniclog at:


when the directadmin.conf setting is present:


This check will be done every minute.

If the file exists and has a size greater than 0, then a notice will be sent to all Admins in the Message System.

DA will note the time of this send in the file:


with setting and timestamp, eg:


So the next minute, when DA sees that the paniclog is still greater than 0, the sent won't occur again until 24 hours has passed.

If the size is still more than 0 bytes, it will sent again.

Note, if you want to disable this feature, change the directadmin.conf setting:




I decided to do a filesize check first, before checking the last send, because it's far more efficient than reading the admin.conf, sorting, and reading the exim_paniclog_last_sent setting.

So a filesize check every minute has a very low resource consumption, vs admin.conf read/sort/load/check.


The nightly full tally will check the admin.conf.

If the exim_paniclog_last_sent value is not set to 0, it will reset it to 0.

This does contradict the checks, above, since 24 hours would never occur, but we'll leave it anyway, in case we want to set a lower time limit.

This would let whatever happens first be able to trigger more sends.

Ability to set DA binary OS in directadmin.conf new

New internal setting:


which can be set to tell the call to /cgi-bin/daupdate to use a specific OS, rather than the value set in the license on our end.

The values are the "value" from the OS selection dropdown when ordering a license, eg:

For ES 6.0 64:


For ES 7.0 64:


For FreeBSD 11.0 64:


For Debian 9 64:


for example, where spaces must be swapped with %20 as DA passes it as a raw value, and apache won't accept spaces in the GET request, else it throws a syntax error.

You'll only ever set this if you need to grab different binaries, and are unable to change in on our end (eg: you don't have /clients access)

If you do not want this set, you must remove the os_override= completely from the directadmin.conf else it will still be used and will likely break the download.

Ability to send POST data to plugin via stdin new

Previously, the only way to get POST data to a plugin was through the environment.

This had upper limits to it's length, as the env does have limits.

This change allows DA to send the entire POST data to the plugin via stdin.

To have DA do this, you'd send the GET variable:


and if that is get via GET, DA will set the environment variable:


Here's a sample usage, eg:



if ($P == "stdin=true")
        echo "Got some info from stdin!!<br>";
        echo "<textarea rows=10 cols=120>";

        $data = '';

        $stdin = fopen('php://stdin', 'r');
        while (($buffer = fgets($stdin, 1024)) !== false)
                $data .= $buffer;
        if (!feof($stdin))
                echo "Error: unexpected fgets() failn";

        $POST = array();
        parse_str($data, $POST);

        echo "&#92;&#92;From: $data";

        echo "</textarea>";


<form action=?pipe_post=yes method=POST>
<input name=data1 value=apple>
<input name=data2 value=orange>
<input type=submit>

Adjust the 1024 chunk size as needed.

DA's pipe send does try to force the entire upload size in the first chunk and then, and uses the write return coded size to determine how big to send the next chunk, with the updated pointer position in the data.

Let us know if this is an issue for anyone and we can change it to send specific/restricted chunk sizes, like 1024 or 2048.

Might not make a different as packet sizes are still the max chunk size and you should still chunk the receiving end anyway.

Note, this is not a "direct" socket from the client to the plugin.

DA has already parsed it (if it's multi-part) and the whole thing is stored in ram.

This is not the most efficient, but just how DA does it, since the request is 100% parsed first, before any actions are done.

File uploads in multi-part are piped to disk and not stored in ram, so the multi-part file upload would be the only "todo" for this feature.. if it's needed.

Not tested for multi-part posts (file uploads).

This method wouldn't contain the posted file anyway, as DA parses that out of the posted data and only sets the filename.

If you need a file upload, try a normal post (non multi-part)... although that's not tested either.

BFM: Added mysql failed auth checking new

Newer MySQL/MariaDB entries will log values like this to the .err log:

2017-12-29 20:03:09 140587380020992 [Warning] Access denied for user 'root'@'' (using password: NO)

In the logs:


or Debian/FreeBSD:


A new directadmin.conf variable, internal default:


or for Debian/FreeBSD:


where the value set is parsed for |HOSTNAME|, becoming for example:

[root@server directadmin]# ./directadmin c |grep brute_force_mysql_log


at directadmin.conf read-time.

Note that many older mysql versions don't add the failed log entries by default.

Some require setting log_warnings=2 or log_error_verbosity=2 (or higher)

See the documentation for your given mysqld server version for more info on the "warning" level of logging.


to shut off the feature just set:


in the directadmin.conf, and it won't parse the err log at all.


If you've set some other log path in your /etc/my.cnf, you can tell DA about the new path, with (for example):


The |HOSTNAME| token can be tokenized in a custom value, in case you need some other path.

Although, you could just set the value hardcoded as well to rule out any issues.

|HOSTNAME| token

This value is taken from the directadmin.conf servername, so ensure that's correct and matches your actual hostname.

DA already has a nightly check to let you know if `hostname` doesn't match server.


brute_filter.list, new value:

mysql1=text=[Warning] Access denied for user '&ip_after=@'&ip_until='&user_after=user '&user_until='

Direct Cron listing. new

New option, disable by default (for now) where the cron listing for Users (and Admin Level -> All User Cron Jobs) is taken directly from the live:

/usr/sbin/crontab -u username -l

output. This will allow a cron manually by a User in ssh, to be visible in DA, even if the cron was not added through DA.

So this is more for advanced Users who do a lot of cron changes with ssh, etc..

This will also be somewhat slower due to a crontab call, instead of a simple file read (only noticeable with Admin Level -> All User Cron Jobs)

Internal default:


to enable this feature, add:


to your directadmin.conf, and restart DirectAdmin


for backwards compatibility with editing and deleting crons, the ID method will still be used.

However, IDs are assigned all all cron entries from 0 going up at the time of the crontab read.

The /usr/local/directadmin/data/users/username/crontab.conf is still written and will still store data, but it's IDs listed cannot be considered synced with what DA is showing.

For example, when you delete a cron, that entry is removed, possibly leaving an ID hole in the username/crontab.conf

BUT when the User loads the updated page in DirectAdmin, the IDs will be based on the crontab -u username -l output, and not the crontab.conf file.

The only thing you really need to consider with this is that if you are manually adding/removing crons with ssh, be sure to have a fresh User Level -> Cronjobs page load before deleting an old cron.

If you load the Cronjobs page, then manually delete a cron from ssh, then delete a cron from the page, your IDs will be off, likely deleting crons you didn't intend to delete.

The same catch could be argued for the old way of managing the IDs in the crontab.conf file.. for example you have the page loaded on one screen, then add a new cron and delete another on a different page or script,

then delete a cron back in your stale load, you could be deleting a cron you didn't want to delete.

So just use common sense and note that when using this, be sure you don't have a stale page/id list when deleting a cron from the GUI.

This was done for backwards compatibility with the ID method of cron management.

Ability to skip adding an IP to the network device (SKINS)(LANG) new

In some cases you might want to manage the adding of IPs to your device yourself.

In these cases, DA shouldn't attempt to add the IP to the device.

A new option at:

Admin Level -> IP Manager -> Add IP

now shows a checkbox for "Add to device".

If you add an IP with this option un-checked, the IP will not be added to the network device, but will be added to the configs for use.

If you don't add the IP to the device on your own, services (nginx) will fail to start, since they require IPs in the configs to be working in the device.



if the option is un-checked, then the following will be added to the above ip config:


so that upon reboot, the startips script (which calls the addip) will skip the adding of this ip.



this script has a check for add_to_device=no in the ip config, so if set, a zero exit code is returned with echo:

IP $1 has add_to_device=no set. Skipping



add this, which lets DA know the option is in play:

<input type=hidden name='add_to_device_aware' value='yes'>

If you do not post add_to_device_aware=yes, then it will be assumed to always add to the device.

And the actual option:

<input type=checkbox name='add_to_device' value='yes' checked>

Always pass this to use the option:







no add_to_device at all (which is what a checkbox does when unchecked, hence the need for "Aware" option, above)

can be used with CMD_API_IP_MANAGER




LANG_ADD_TO_DEVICE=Add to device

LANG_ADD_TO_DEVICE_TITLE=Add IP to configs, but never to network device

Ability to block special characters in domain names new

Newer version of named don't like special characters.

By defaults directadmin allows them, as older versions did accept those characters.

A new option has been added, with internal default:


If you want to block Users from adding domains with special characters, set:


and restart directadmin.

Ensure there are not already domain with special characters (greater than ASCII 127).

If Users run into this error, then they must add their domains to DirectAdmin in pundy-code format, see link below.

Once DirectAdmin fully supports IDN/puncode domains, then it will automatically convert IDN domains into punycode format, and this setting will default to 0.

default_ttl=14400 directadmin.conf option (TEMPLATE) new

New internal default setting:


which is the default value used for zone TTL values.

Changing this setting alters what all TTL values have for all records, zone TTL,etc..

You can still override the TTL of a User domain, regardless of this setting.



new token added:


which is set to the default_ttl directadmin.conf setting.

Changes in the named.db also include setting a local variable, and setting all default times to that local variable, eg:


so that you can simply alter it once, eg:


although... the need for this is rare, since you can now do it in the directadmin.conf.

However, it might be handy for if-then-else scenarios, like:

|*if DOMAIN=""|

but again.. you could just set User domain in the GUI already.

But non-User domains that might not have a hosting account, you could use this if-then-else method to set a specific TTL.

DKIM: ability to use selector instead of x new

New directadmin.conf with internal default:


where you can set something else, eg:


by adding it to the directadmin.conf and restarting DA.



Note: this does not alter the dkim settings in the exim.dkim.conf file.

If you do use DKIM and want to use some other selector, then edit the file and set it however you want, eg:

  dkim_selector = y

you might want to lock it:

chattr +i /etc/exim.dkim.conf

to prevent CustomBuild from overwriting it during a;

./build exim_conf


CustomBuild 2.0 rev 1824

will automatically swap the selector in the /etc/exim.dkim.conf, just run:

./build update

./build version #to confirm at least rev 1824

./build exim_conf

Rename hostname zone during hostname change new

The "How to change your hostname" guide:

Uses two steps, where the 2nd step is to manually create the 2nd zone.

This change will replace step 2 by simply renaming the old hostname zone, if it exists, to the new value.

This is done internally in DA, rather than in the, because of the large number of actions done for dns writes (templates, pre/post script, dns clustering with MSS), rather than fighting with a mess of regexes and task.queue pushes.

New directadmin.conf option with internal default:


so if you regularly change your hostname, but do not want DA to change the zone associated with it, set:


pipe_post=yes for all pre/ scripts new

If you set a GET value of:


then the POST data will be sent to the pre/ scripts via stdin.

DA will still try and load everything else into the ENV anyway, but long values are chopped to length limits, so if you're trying to get everything from POST, this is how you'd do it.

DA will set env var:


if the above has happened, so you know to check for it.

Include ?pipe_post=yes in the GET portion of the method=POST request, eg:

<form action='CMD_SOMETHING?pipe_post=yes' method='POST'>

Then in your script, the posted data can be read in like this:


if ($P == "stdin=true")
        echo "Got some info from stdin!!<br>";

        $data = '';

        $stdin = fopen('php://stdin', 'r');
        while (($buffer = fgets($stdin, 1024)) !== false)
                $data .= $buffer;
        if (!feof($stdin))
                echo "Error: unexpected fgets() failn";

        echo "Data: ".htmlspecialchars($data);


if you want the POST to always be set, regardless if a GET/POST has passed pipe_post=yes, then you can change the internal default value:

force_pipe_post=(NULL) #not set in the directadmin.conf at all, to be:,,etc.

colon separated, no spaces, for a list of scripts you want to force the post to be piped through.

Of course, if no moethd=POST is done, this won't kick in.


If you do set pipe_post=true, but the script does not read any of it, you might run into a full stdin buffer.. may vary or might be fine.. just be sure to actually read the data if you're using the option,

or DA may be trying to push data into a pipe with a full buffer. But if your script quits, this should close the pipe, and DA would stop sending... so in theory it should be fine.

File Uploads won't show you the whole file, since that would already be stored in a temp area.

You'd get something like the file0 variable pointing to to the actual location on disk, prior to it being moved by DA (should that be the instructions called, like FM file upload).

POST only case

in some cases, like saving an edit file in the FileManager, GET cannot be used at all.

An exception has been added where cases such that the custom variables passed to the .sh scripts may be a direct dump from the GET+POST already.

In these cases, when the full dump happens to be passed anyway (eg: including the FileManaged edit "save"), you can include:


to the POST value, and everything else should continue normally.

Of course, this means you'll get pipe_post=yes passed in your POST data, but you'd just decode it anyway.

Option to hide login-as in login history new

New internal variable, with internal default


which tells DA to login any "login as" to a User in that User's login.hist file.

If you never want your clients to know you've logged into their accounts, then set this to 0:


in the directadmin.conf, and restart DA.

Note, you should still get a history in:


of any login-as, so it can still be tracked.

letsencrypt_multidomain_cert=3 for only current domain and pointers new

The previous feature:

Let's Encrypt: Ability to select which DNS records to include in the certificate (SKINS)(LANG)

allows for:


where, "If set to 2 (internal default), then it adds the other domains under the User, plus any domain pointers under those domains."

This new change allows for:


which only loads in the current domain and it's pointers.

Other domains under the User account are not listed.

This will be enforced during a post, because the internally loaded list is used to verify allowed values

Check exim queue size before trying to load it new

When accessing CMD_MAIL_QUEUE to view the mail queue, DA will now call:

exim -bpc

before trying to load the queue.. on the assumption this command uses other means (cache?) to count the files, rather than actually reading all messages, which is slow.

New internal default value in directadmin:


If the result of exim -bpc is greater than mq_exim_max_load_size, then an intermediate warning page is shown, with a button to try anyway.

This will add the GET values of force=yes telling DA not to worry about it and show it anyway.

When forced, the initial exim -bpc call is not done, in case that call itself is slow, where it's not needed since we're going to jump straight into loading the queue no matter what.


when json=yes is added to the GET, the this is a sample output:

        "error": "true",
        "mq_exim_max_load_size": "2000",
        "queue_size": "2001",
        "text": "Queue size (2001) is larger than the maximum value (2000) and may timeout. Click to try anyway.",
        "use_request": "sort1=2&json=yes&force=yes"

where the use_request takes the current GET (no matter what it is, sorts, etc..) and adds force=yes to the end.

This may help in the event a specialized search is done, so that it's preserved during the force.

If you call CMD_API_MAIL_QUEUE and hit the limit error, then it's a URL encoded string, rather than json format.

But same variable names and values.

php-fpm default pm.max_children=100 (TEMPLATE) new

Increase the default pm.max_children setting from 50 to 100.





Ability to disable letsencrypt on a per-User account basis new

With letsencrypt=1 enabled globally, you might want to deny access to LetsEncrypt for specific Users.

With this change, you can do so by adding:


to their user.conf at:


and if present in the user.conf, it will override the loaded global value.

Note that this only applies to the interface, and does not affect background/dataskq actions.

So this will not work to globally have it shut off, if you're trying to enable it for 1 User, for example.

The background checks must have it enabled globally to work.

This is a very crude implementation and does nothing more than shut off the letsencrypt options in the interface for this User.

It will also block any LE requests made by the User, since it will appear to be shut off for any call to CMD_SSL or CMD_API_SSL.

Option to skip LetsEncrypt auto-renew if domain is suspended new

New internal default:


Where a suspended domain will not have it's LetsEncrypt certificate auto-renewed.

If you want suspended domains to be renewed anyway (old default), then add:


Reasoning was because domains may expire or there are issues where the LetsEncrypt will attempt an autorenewals,

and the Admin might get repeated failure notices, so this would allow them to simply suspend the domain as a quick solution.

The other pre-existing way to stop renewals is to login as that User, go to the SSL Certificates page, and click the disable auto-renewal button, at the bottom of the page.

Php selector can pick "Off" for second php instance new

If you know you have no use for the 2nd php instance, you can now set as "Off" for a given domain:

User Level -> Domain Setup -> -> PHP Version Selector

Where "Second PHP" can be set to "Off", which is saved as:


in the:


The effect of this is that the VirtualHost will not have PHP2.. so things would end up being set like:


The main point of this is to allow for the removal of an extra php-fpmXX.conf file, eg:


if they're not needed.

During an httpd.conf rewrite, the php-fpmXX.conf files are re-written.

Any domain with the 2nd php instance set to 'Off' won't have the php loaders added into the httpd.conf.

If DA didn't count any using a specific php version, it will be fully shut off by removal of that php-fpmXX.conf file.

The ~username access for <Directory "/home/user/public_html"> is controlled by the settings from the main/default domain in the account, so these php loaders will also be reduced if php is "Off".

hook script: new

New custom hook script can be created here:


which is called in the event that a reload and restart both fail to set a service to be up.

This will give you the opportunity to add extra checks/fixes if you know of common issues with the given:




If your script thinks it might have fixed the issue, then exit with a return code 0, which tells the datskq to wait 5 seconds to let that service start up before checking if it's running.

For all other cases, the script should return a non-zero status, eg: "exit 1" which disables the 5 second wait for the final process check.

If the service is not running for the final process check, then the dataskq sends the standard notice to all Admins, eg:

"The service 'servicename' on server is currently down"

In the even your script is called, all relevant logs will be loaded with the result:

try_to_fix_process(%s): error: %s


try_to_fix_process(%s): success: %s

where the first %s is loaded with the service name (eg: httpd) and the second %s is filled with any echo/output your script generates from stdout + stderr.

MD5 checks on DA update.tar.gz downloads new

The daupdate.tar.gz was already adding the X-md5 header.

DA will now check this header against the md5sum after download.

If all md5 hashes are valid, but they don't match, extraction won't happen and an error will be reported.

No retry will happen, but you can try again anyway (might have been httpd restart on our end, rare but can happen)

Restore ns1/ns2 and subject to reseller.conf fixed

The 3 values:




were not being restored to the reseller.conf.

Restore DNSSEC keys fixed

The DNSSEC keys are already in the backups, but were not restored.

This change will now restore them.

Note, if you do not have:


set in the directadmin.conf, the keys will still be restored but not signed.

So if you forget to turn on dnssec before restoring, just turn it on and sign the given zone with the restored keys.

Backups to use umask 0077 fixed

Although the folders are always set securely be default, Users could still change their backup folder permission to 755 by accident.

As a precaution, temporarily change DA to use umask 0077, then back to whatever it was before.

This lets the tar.gz be built with chmod 600, instead of 644.

As mentioned, the default folders permissions should already all block any security concerns with it, but Users are prone to errors, so this is just an extra layer of security. not run in some cases fixed

The check for the script was not being done as root, so if the script was not world-readable, then it wouldn't be called (still called as root, but not called if not seen by "nobody")

Workaround is to chmod to 755, assuming other directories are also 711, or just grab this fix.

php-fpmXX.conf: add .inc to security.limit_extensions fixed

DA will now include .inc in the list of extension to be added to security.limit_extensions in the php-fpm.conf template.

It's set in the |LIMIT_EXTENSIONS| tokens, so no need to alter any template files.

Here's the code diff for setting the token:

-                tokens.setParam("LIMIT_EXTENSIONS", ".php .php52 .php53 .php54 .php55 .php56 .php60 .php70 .php71 .php");
+               tokens.setParam("LIMIT_EXTENSIONS", ".php .php52 .php53 .php54 .php55 .php56 .php60 .php70 .php71 .inc .php");
               tokens.extendParam("LIMIT_EXTENSIONS", tokens\["PHP_VER"\]);

Check remote subdomain owner fixed

Multi-Server Setup (MSS): check subdomain owner

Lets assume you have box A ( and box B (

B has a domain

A has the "Domain Check" enabled for the IP of B in the MSS.

a User on A tries to create This correctly fails with "Domain already exists on"

However, if a User on A tries to create with the "Subdomain owner check" enabled on A, then this is incorrectly allowed.

Bug fix is such that the zone check from A to B will now add:


making the full check be:


where, DA on B will first check all zones normally for

But when check_for_parent_domain=true is passed (which it will be now, from A to B), then a secondary check is done in the /etc/virtual/domainowners file.

if exists in the domainowners file, then the result of the above CMD_API_DNS_ADMIN check will return exists=2 (instead of exists=1), meaning the match was a parent domain of

Usernames have no effect on this check.

Any existence of a parent domain in the domainowners will trigger exists=2.

As with the local subdomain owner check, any "Admin" account will not have the subdomain owner checked at all.

Neither locally, nor remotely.


UPDATE for 1.56.1, check will also apply to zones without Users:

Subdomain owner check for remote dns servers without Users

NOTE: For stand-alone DA boxes as DNS servers:

Say you have User boxes:


and both of them are pushing zones to 2 stand-alone DA DNS servers, with Multi-Server Setup (MSS)


For u1/u2, you'd usually have both ns1/ns2 listed in each with:

  • Zone Transfer
  • Domain Check

enabled, so zones from u1/us2 are pushed to both ns1/ns2.

However, with regards to the setting:


because the owner of does not actually exist on ns1/ns2.. only the zone, the "owner" of the domain cannot be done (that domain does not exist in the domainowners file)

So for this scenario, you'll also want to have u1 and u2 see each other, but not do a zone transfer.

So on u1 MSS, add another host:

with checkboxes for:

  • Domain Check
  • User Check

and similarly, on u2 MSS, add:

with checkboxes for:

  • Domain Check
  • User Check

This ensures that:

  1. u1 and u2 will not have an overlapping Usernames, so the "owner" is unique per server.

This ensure that if you have "fred" on u1, and a different human/domain on u2 with User "fred", that u2:fred cannot create a subdomain for u1:fred's domain names.

  1. Domain Check, the subdomain owner check actually works, since the domainowners file exists on both boxes with proper domain/owner info, so the subdomain owner check actually works.

Long DNS entries use
to wrap: replace with word-break:break-all; white-space:normal fixed

Swap the long dns entries form using a
to a browser wrapped line, using css word-break:break-all; white-space:normal

This is to prevent the need for
and the newline, making copy/paste more "correct" in preserving the original data.



Setting didn't stick in reseller.conf if an Admin edited themselves fixed

Bug where (for example) you disable ssh for "admin" as "admin".

The function would be successful in the /etc/passwd and /etc/ssh/sshd_config, but because the live reseller.conf is saved after the call, the modified file was lost.

Extra check for "self editing" to load the changes into the live reseller.conf, so when it's re-saved, it has the new values.

domain rename: include cert.combined fixed

When a domain is renamed, be sure to rename the .combined file.

Also found/fixed another cert.combined.combined bug during the mail_sni rewrite.

Changed to a custom user process kill, during User removal fixed

UPDATE: September 1, 2018: Bug found, fixed for DA 1.53.5:

killUserProcesses: was not killing User processes


Only reported on CloudLinux boxes. Not sure if it's caused by a recent CL update.

May undo this change if it's found that it has no effect.

the userdel call is going defunct, and DA is left waiting for it's return, which never comes.

Suspect wast this recent change to killall:

Change to use use killall when removing User

but some strange behaviors started to pop up where it would end up killing bits of the directadmin process, even after the killall had returned.

It's possible killall (possibly just in CloudLinux, not sure) where it sticks around after the fact to make sure all processes are dead (just a theory)

Changed to instead delete the User first, and then go through all processes that were running at that old UID number (noted before User removal)

Each of those UID based processes are removed while running at that UID, to reduce the chance of issues (the list is obtained while running as root.. we don't want to commit suicide)

Then this is all done one more time, in case a master process had spawned an other child after it's SIGTERM (unlikely, but good to be sure)

User suspension will still use the killall method (no reports of issues, as userdel isn't run during suspension)

after testing, this change was found to have no effect at all.

May undo it.

More info will determine the course of action.

Clear extra access hosts for system account during db removal fixed

Bug where if you have an extra access host on a DB, let's call it:

when you delete that DB, the system account "user" will still have the access host in the mysql.user table.

Fix was to run a query before the DB removal, that clears all instances of that user, where the access host does not exist in any other DB in mysql.db.

Query logic is:

DELETE FROM mysql.user
(SELECT host FROM mysql.db WHERE (db!='username_db' AND db!='username\\_db'') AND (db LIKE 'username_%' OR db LIKE 'username\\_%'))
AND user='username'

MariaDB: Error inserting host: Field 'authentication_string' doesn't have a default value fixed

Newer versions of MariaDB don't have a default value for the authentication_string setting in the mysql.user table.

This causes an error:

Error inserting host: Field 'authentication_string' doesn't have a default value

anytime you try to add an access host.

The fix checks to see if there is an authentication_string field in mysql.user before duplicating the row for the given user.

If there is, then the query also specifies the authentication_string and '' empty default value.

Note, the above is not for MySQL 5.7 which actually uses the authentication_string column.

If you're not running MySQL 5.7, then you must have:


or else authentication_string is loaded with the actual password, causing another level of confusion.

To clarify:

MariaDB must have mysql_milestone_16=0.

MySQL 5.7 must have mysql_milestone_16=1

Any other older version of MySQL must have mysql_milestone_16=0.


A temporary work-around is to manually set a default value:

Set use_xfs_quota=0|1 as needed at install time fixed

By default CentOS 7 DA binaries have use_xfs_quota=1 set internally.

This check, done in the on CentOS boxes script will now check if the current quota_partition (/ or /home) has xfs enabled,

and if it does, and doesn't match the internal default will change the data/templates/directadmin.conf so it uses the correct quota format right at install time.

message/ticket URL to show correct type (TEMPLATE) fixed

The email notices for the Message System and Ticket System, using the template files:



had a URL with trailing GET value:


This change will now show the correct type, if it's a notice of direct message (that cannot be replied to), as:


by swapping it to:


in the 2 template files.

This doesn't really affect much in terms of the Enhanced skin, but the Evolution skin needs it to handle the redirect to a specific page type.

Add linked IPs to pointers on restore fixed

Bug where the linked IPs were not being set.

The code was getting the list of pointers before they were created, hence nothing was added.

Moved the adding of linked IPs later one, once the file existed, thus allowing the linked IPs to be correctly added to the pointers.

Anonymous ftp: set_crypt_for_anonymous_ftp fixed

New internal default:


where, if you set this to 1:


by adding it to the directadmin.conf, when DA adds an account to the /etc/proftpd.passwd (and /etc/pureftpd.pdb), it will use a crypt from a blank string.

So the net effect is still that a blank password is still used for anonymous logins, however, it seems that newer versions of something (perhaps crypt, perhaps a system library) no longer like an empty crypted value,

and now require a crypt, even if it's of a blank string.

Bug with pure-ftpd configs:

init.d, edit:


For systemd, edit:


change the OPTIONS line from

-u 100

to be:

-u 99

since anonymous logins use UID 99 (nobody)

Some servers will now have a config at:


If you have this file, edit it and change:

MinUID                       100

to be:

MinUID                       99

No need for restart with action=quotatally fixed

There are various types of tallies, typically involving the rotation of logs, hence the need for reloads of the services like apache/php-fpm.

During the deletion of a domain, the services are all restarted, and a mini quotatally is also added to the todo list.

The quotatally itself would then trigger another reload of service, because it's a tally, but for this once case, the restart is not needed.

The first restart would have happened normally (to remove VirtualHosts, etc) but the 2nd restart isn't needed since no logs are rotated during a quota tally (which runs in the 2nd minute dataskq cron, next time around).

This change is to check for action=quotatally, and disable the restart (reload/graceful) of apache (and thus php-fpm) afterwards, since it's not needed.

DNSSEC for User Level domain pointers fixed

When DNSSEC is enabled, the form will now correctly be filled with:

so the dnssec records can be created on the pointer.

Last Updated: