Version 1.52.0

Released: 2017-10-03

http2 new

Support for http2 in the templates.



in the directadmin.conf.




In the custombuild apache/nginx configure templates;

configure/ap2/conf/extra/httpd-vhosts.conf:<VirtualHost |IP|:|PORT_443||LINKEDIPSSL|>

Note, CustomBuild 2.0 rev 1600+ can setup http2 for you for Apache, but it doesn't install openssl with ALPN for you.

This is related:

So with CB2 now, you'd do:

  1. Add a non-standard instance of apache/nghttp2 to use:
tar xzf openssl-1.0.2k.tar.gz
cd openssl-1.0.2k
./config --prefix=/usr/local/lib_http2 no-ssl2 no-ssl3 zlib-dynamic -fPIC
make depend
make install

DO NOT USE 1.0.2h or newer, as they've dropped older SSL functions that apache 2.4.26 does not know about .. so you'll get compile errors if you use anything newer thatn 1.0.2k.

  1. Set:

in the directadmin.conf, eg:

  1. Let CB2 do the rest:
cd /usr/local/directadmin/custombuild
./build update
./build nghttp2
./build apache
./build rewrite_confs

For Chrome, you'd need the SPDY extension to access HTTP/2 sites.

CMD_PLUGINS ability to skip user.conf write new

A plugin may need to make an API call to change a user.conf setting for the current User.

This is a race condition, since the CMD_PLUGINS call to that plugin, follows up with a check on the user.conf timestamp (and related files),

and if DA notices the timestamp changed, it will decide to overwrite it and log something like:

2017:06:11-01:05:53: ConfigFile::writeFile(./data/users/fred/user.conf) : Timestamp from when it was read is different, overwriting anyway

The solution is to tell DA not to write to disk when you know you'll need to be doing that.

To accomplish this, pass a GET request:



and this will stop the user.conf write for that call to CMD_PLUGINS.

You'll then be able to make an API call within the plugin code, to alter the user.conf, without racing to write first.

Ability to message Users from task.queue new

Related to this guide:

Ability to script/send your own custom notifications to the Admin Message System

you can now message a specific account, list of accounts, or all accounts (Admins+Resellers+Users) with the task.queue

Format is as follows:

subject=Your Subject
message=Content of your Message

where the "users" variable can be "all", meaning DA will message all accounts on the server,

or can be a list of accounts, url encoded with select1, select2,

For a single account, just use:


Note.. the = and & characters in the users list must be url encoded with hex (%3D and %26, respectively)

Also, if you want to include a newline in the message, use %0A instead.

A sample echo command is:

echo 'action=notify&value=users&subject=Hello World&message=Hello,%0A%0A This is a message.&users=select1%3Dadmin%26select2%3Dgary' >> /usr/local/directadmin/data/task.queue

json for CMD_FTP new

Support for json on all CMD_FTP commands:


The json output uses the API functionality, so use the above commands, but use the CMD_API versions of those commands for info on how to use them:

Api for ftp : CMD_API_FTP

Ability to tokenize CSS_* files. new

By default CSS_ files are sent as raw file for quickest performance.

This change allows you to add this get request, eg:


where adding tokenize=yes to the CSS will force DA to tokenize it with all global tokens.

You can also set variables, and do standard if-then-else structures, etc, like like HTM requests.

This change was added for the custom color feature, so a signle token can be set anywhere in the skin and CSS files.

CMD_SKINS custom skin colors json/api (SKINS)(LANG)

Ability to include custom other disk usage for a User (LANG) new

If you have data that should be counted in the total disk usage for a User, but does not fall under the standard usage areas (eg: data on a remote server),

then you can use this option to create a hook, which lets you add extra bytes into the disk usage under "Other Usage"

Set the following in the directadmin.conf:


and create a script:


the script must exit with code 0.

If a non-zero code is exited, the output is logged to the errortaskq.log.

The output on exit 0 must be a URL encoded.. and for now, it will basically just be:


where 12345 bytes will be added to the user.usage file.

In the future, we might add more, like


but for now, it's just "other_quota".

The value must be a positive integer.




runs as root.


If you call CMD_SECURITY_QUESTIONS with GET option:


you'll get the output in json.

See the contents of enhanced/user/security_questions.html for the usage of the tokens.



Most variables should all be self-explanatory, but the "num_questions=22" will be confusing by design.

The security_questions.txt file starts at 1, but index 1 must contain the last index number.

By at the time of this writing, it's:


where the last entry is 22, eg:

22=What is the name of a college you applied to but didn't attend?

Also, because 1 is taken, the first question starts at index 2.

The json variable "questions" is standard table output, with the json[questions][info] showing info on columns, sorting, etc.

When posting data, just like the API methods, include json=yes (GET or POST) and the dynamic output should be json-ified.


this value will be set to:


if the number of questions is 0.

If this is set, then the checkboxes for:

  • security_questions

should be disabled, because you cannot turn on the feature, with 0 questions available for DA to ask the client.

brute_filter.list supports count_multiplier option new

A new option has been added to the lines in the file:


specifically for the wordpress3 entry, eg:

wordpress3=ip_after=&ip_until= -&text=] "POST /&text2=/wp-login.php&text3=" 302%20&count_multiplier=4

What this does, is after all log parsing is done, the Brute Force Monitor will count how many entries were triggered for that item (eg: wordpress3) for that given IP.

Say there were 20 triggers. The multiplier means that instead of having a limit of 100, that item needs 400.

The actual logic in DA is actually backwards, as the total count uses other items, so we actually divide the number of wordpress3 counts by 4.

So instead of 20 hits, the count only see 5 towards the total.

This also means that is there are only 3 hits, then 3/4 = 0, so no count would be triggered for that IP.

The entries will still show up in the log list, but count for that given IP (top table) would be lower.

The whole purpose for this is that the entry:

POST /wp-login.php HTTP/1.1" 302

could represent an attack, but could also just be a normal redirection.

So we do want to count it, but it should be of lower significance.


There are no html changes required.

However, we have changed the |LOGINFAILURES| table token value.

The wordpress3 attempts, which are usually 1, will now be displayed as a float / multiplier.

So for this case, each wordpress3 attempt count will show 0.25.

There is also a title= hover-over popup to provide info about what that's about.

skin.conf 404 override new

You can now add a new variable, eg:


to the skin.conf file.

This setting allows you to control the output of the 404 pages by instead, sending a specific page, with the data set in the desired file (eg: user/404.html, can be changed).

This file is tokenized similar to an HTM_ file.

Note, the FileManager is chrooted, so if you trigger a 404 for a file that does not exist, you'll still get the old format because the 404_override file is loaded after the chroot has already happened.

Also new optional file in the skin document root:


which will list valid paths that will return 200 instead of 404 for the 404_override file.

Intended purpose is for vue-router / JS framework to redirect things like:

to valid places via JS using the 404_override file.

The same file is sent regardless of the routes.conf list, but the list simply controls if it's a http 200 or 404 response code.

The routes.conf supports wildcards, similar to the shell (cannot cross / character)



will mach:


but will not match:


Referer check override files (PLUGINS) new

New optional file can be created:


or per-plugin:


where the referer_check.allow files can contain a list of URLs, eg:

etc..which will be used as a backup list of allowed referers, in addition to the host value set in the session file.

This is used for cases where a remote site needs to use the current User's session file, via a form on that remote site.

As this is bypassing a security step, be sure you fully trust the list of referers added to your lists.

As you may have noticed, you can either manage your own global list,

but each plugin can also have it's own list as well, in case the plugin needs to make a call out, then back in again.

ACCESS_LEVEL token on all CMD_ pages new

A new global token:


is available on all CMD_ pages.

The values is based on which class list of functions it's allowed in.

HTM_, CSS_ files, etc.. will not get this token, but could be figured out by coding into the given skin.

eg, if you know the user/passwd.html is a User page, you'd just add:


to the top of the user/passwd.html file.

CMD_JSON_LANG .po files for languages, and locales new

Extension for the CMD_JSON_LANG tool:

  1. If no options are returns, a json output will show something like:
        "id": "en"
        "en": "English",
        "fr": "Français",

depending on the languages available for this skin (we only include "en" by default)

The "en" can be any valid language, and the rules have been related to allow proper locale naming, like en_US.

Each value for that local on the right will be in that native language, not English.

So for fr, it will show Français, and not "French".

The actual locales.po file should exist the skins document root, eg:


If if it doesn't exist, DA will fall-back to use the "internal_lang" directadmin.conf, variable, but access the po file using the .. method eg:


allowing the fallback to be used, even if the client has some other skin set, for the event where the other skin does not have locales.po file.

TODO: Fill with all languages/locales.

  1. WIth a lang value, eg:


it will dump json for the given .po file.

A language po file can exist either at:


where "lang.po" is the literal filename for the current LANG...



where LANG.po could be any valid lower-case language or local name (instead of a directory), eg:


  1. With everything:


will include the array from #1, but will also have:


containtaining the entire json array for the whole defautl language po file, eg:

            #whole json output for "en" becaue id=en.
        "id": "en"
        "en": "English",
        "fr": "Français",


If you include GET:


you'll get json output.



    "result": "success",
    "success": "logout"

if you were "logged in as" the User, and end up as the creator, you'll get:

    "current_user": "admin",
    "result": "success",
    "success": "creator_logout"

where "admin" is the creator.

If a LOGOUT_URL was specified at login time, a value:


will also be present at logout.




note that is is a POST, not a GET for the login. GET will not be seen.

During login, if json=yes is included with the POST, you'll get output like:

    "json": "yes",
    "referer": "/",
    "success": "yes",
    "username": "admin"

If FAIL_URL was specfiied at login time, you'll get it in the output, eg

"FAIL_URL": "",


the LOGOUT_URL will also be shown if passed, but not likely you'll be using it.

http2=1 enabled by default for supported OSs new

Debian 9 has ALPN compiled into openssl by default.

DirectAdmin will use an #ifdef to control the default state of the http2= setting in internal directadmin.conf settings.

The actual directadmin.conf will still override it.

So the short version of this is, Debian 9 will have http2 enabled by default.

CustomBuild 2 will read the DA setting and adjust the httpd.conf accordingly.

Note that mod_php won't work, since http2 won't support prefork anymore.. so we may set Debian 9 to use php-fpm by default.

Custom Package Items skin up to 8 items (SKINS) new

The internal custom package item limit is 99.

However, the skins only had a set number of tokens filled in, previously 5.

This change increases that to 8 in the enhanced skin.


CMD_CHANGE_INFO can set temporary language in session new

Relating to the usual way User would change their language:


this adds an extra option:


such that it will save the language into the session file instead of the user.conf.

Then any future pages loaded, the language set in the session file will override the user.conf language.

This is useful if you have multiple people using different languages, logged into the same account at the same time.


language=<any text>

This will also affect the global |USERLANG| token.

Skins can provide their own login.html page or images (SKINS) new

If a skin creates the file:


in it's top level directory, eg:


then whatever skin is set in the directadmin.conf for:


will be search for that login.html, and used if it exists.

The priority order is:

  1. /usr/local/directadmin/data/templates/custom/login.html

  2. /usr/local/directadmin/data/templates/login.html

  3. ${docsroot}/login.html

  4. internal login page


Similarly, you can now create your own skin's "login_images" path, eg:



JSON support has been added for the commands:


where you simply add the GET request (not POST)


and JSON is output.

The format for all 3 is similiar, the existing table tokens are simply converted into json, so that we can maintain the searching, sorting and filtering features of the table class.

Like other json table output, the format is a list of numbers, representing the row index displayed.

Also an index called "info" is shown, to give info about the table:

        "username": "1",
        "creator": "2",
        "bandwidth": "3",
        "quota": "4",
        "vdomains": "5",
        "suspended": "6",
        "ip": "7",
        "domains": "8",
        "sent_emails": "9"
    "current_page": "2",
    "ipp": "50",
    "rows": "69",
    "total_pages": "2"

which is the "info" index for the CMD_ALL_USER_SHOW table.

The other 2 (Reseller and Admin lists) look like:

        "username": "1",
        "bandwidth": "2",
        "quota": "3",
        "nusers": "4",
        "vdomains": "5",
        "suspended": "6"
    "current_page": "1",
    "ipp": "50",
    "rows": "5",
    "total_pages": "1"

where the "columns" sub-index represents the values shown in the table rows.


A sample CMD_ALL_USER_SHOW row would be:

    "username": "admin:not_user",
    "creator": "root",
    "bandwidth": "2.76 / unlimited",
    "quota": "18839.4 / unlimited",
    "vdomains": "6 / unlimited",
    "suspended": "no:",
    "ip": "",
    "domains": "|||",
    "sent_emails": "2:1"


  • the username will contain the username, followed by a colon. After that, if it's a user, it will show "user". If it's not a user (admin or reseller), it will show not_user.

This is used to link to CMD_SHOW_USER or CMD_SHOW_RESELLER (even admin's go to their Reseller info page)

  • vdomains is the number of domains on the account, followed by a space, forward slash, space, then the limited (number in meg, or unlimited)

  • the suspend item is similar, where after the colon will be the suspended reason, if set. Can be blank. Example: "yes:inactive", where the reason is from the "reasons" list (see below)

  • ip list is colon separated.

  • domains are also colon separated BUT any domain that starts with "pointer_" is a list of domain pointers, for the domain name contained in the string after pointer_. The pointer list starts after the = character, and is pipe | separated.

  • sent_emails contains the total number of sent emails this month, and after the colon, it's the number sent today. Somtimes there will be no colon or trailing value, eg, "0"

The method is also adds an exrtra index, the other 2 don't have:


will be set to yes or no, to let you know if the "Leave Dns" checkbox should be shown (is controlled by the Multi-Server Setup clustering being turned on)



is very close to CMD_ALL_USER_SHOW, but without some of the columns:

    "username": "testuser",
    "bandwidth": "0.0000 / unlimited",
    "quota": "11.0 / unlimited",
    "vdomains": "1 / unlimited",
    "suspended": "no:",
    "ip": "",
    "domains": ""

it also does not have the "add_leave_dns" index, as it's not relevant for Resellers.



    "username": "admin",
    "bandwidth": "1.0217 / unlimited",
    "quota": "19391 / unlimited",
    "nusers": "45",
    "vdomains": "67 / unlimited",
    "suspended": "no:"

where the info is pretty much the same, but:

  • username does not need to be colon split, it's only the username, since the list is exactly the type you requested.

  • nusers is the number of User accounts created under this Admin or Reseller



all 3 methods will also get a "reasons" index for the selectbox shown at the bottom of the GUI tables.

        "selected ": "yes",
        "text": "-- Reason:",
        "value": "none"
        "text": "Abuse",
        "value": "abuse"


Where the "text" will be from the data/templates/suspension_reason.txt file, so may not be correctly translated (.po lookup?)


This change allows the ability to use CMD_EMAIL_POP? to get JSON output.

The output will vary quite a bit depending on several conditions, so be sure to test all conditions, as each DA box might give different output.

Top level variables:

"clean_forwarders_on_email_delete": set to 1 if enabled Ability to clear forwarder values when deleting emails

"count_pop_usage": set to 1 if enabled: option to disable quota counting for pop accounts page

"EMAIL_MESSAGE": this will usually be an empty string "" but if it isn't, it should be a general info string, shown to the User, eg:

"Until the cache is created, the values may not be correct. Refresh the page in 1 minute."

"pop_disk_usage_cache": set to 1 if enabled: email disk usage cache to speed up the pop page

"pop_disk_usage_true_bytes": set to 1 if enabled: Email Disk Usage opion to show true bytes rather than block usage

If enabled, then you'll need to show apparent_usage instead of usage (see below)

"user_can_set_email_limit": set to 1 if enabled: Per-Email send limit (SKINS)

"block_cracking_unblock": set to 0, 1 or 2, depending on: BlockCracking notices and unblocking (TEMPLATES) (SKINS)

"hide_outlook": usually 0, but if 1, don't show the outlook column with a url like:


"purge_select": select box data for the account purge tool.

"when_select": "select box data for the "when" portion of the purge (eg: all or messages older than x days, etc..)

"emails": main data for all email account, including system account. This returns a "table" version of the json, so has the standard "info" sub-array giving info about rows, column names, etc..

Because it's a standard table, you can search, sort and filter, like a normal table.


A sample row from emails might look like:

    "account": "",
    "login": "",
        "apparent_usage": "12.023",
        "imap_bytes": "25047040",
        "quota": "30",
        "usage": "23.886",
        "webmail_bytes": "0.000000"
        "send_limit": "42",
        "sent": "1"
    "suspended": "no:blockcracking:change_pass"

where this data can vary a lot depending on the values of the global settings above.

"login": this is straightforward, but will be just "user" for the syseem account.

"usage": This is dependant on "count_pop_usage", and if count_pop_usage=0, then only "quota" is shown, or nothing at all for system account.

"sent": if user_can_set_email_limit=0, this will not appear.

"suspended": usually "yes" or "no", but can be "no:blockcracking:change_pass" or "no:blockcracking:no_unblock", where it means the account is blocked via BlockCracking. If change_pass, then you can offer info that it can be unblocked by changing the password for this account, but no_unblock cannot be unblocked.

unblocking info: BlockCracking notices and unblocking (TEMPLATES) (SKINS)

Manually blocking a User can be done by adding them to /var/spool/exim/blocked_authenticated_users in the format:

where the right part is the timestamp when they got blocked.


when coding with this json output be sure to test all of the directadmin.conf settings, off/on etc, and scenarios to ensure you're handling all cases.

Different servers will have different settings, thus different output. and login_key in session new

If a session is to be destroyed due to old age, or due to logout, you can now create:


which will be called just before the session file at:

/usr/local/directadmin/data/sessions/sess_XXX is removed.

Environmental values passed will be the entire contents of the session file, plus:

file=/usr/local/directadmin/data/sessions/sess_XXX #path to the current session file.

Contents of a session file might look like this, but can vary:


Also changed, if a Login Key is used to login to DA, the login_key=keyname is added to the session file.

You can then get info on that login key with path:


Note that this does not account for the "login-as" method which might set the username to be:


so be sure to check for that in the $username variable before using it, eg:

REALUSER=`echo "$username" | cut -d\| -f1

In the above example, the login_key and key pass would be from User "admin" and not "fred".

and use $REALUSER everywhere, instead of $username.


unlike other script, the exit code of your script will not abort the deletion process, as we should never block the removal of a session.

That being said, I still recommend you use exit 0; at the end of your script, in case this policy does change later on.



will now give json output.





can be used.

JSON: large list of added commands new

Implementations for more JSON output.

Add json=yes for GET to show info.

User Level:





CMD_SUBDOMAIN (same as CMD_SHOW_DOMAIN for main list)



Other CMD_FILE_MANAGER json actions here: CMD_FILE_MANAGER action=json_dirs action=json_files action=json_all


Be sure to check for "sending_php_scripts" and:

"block_cracking_paths": "yes",

which only show up if many sends have taken place, hitting some limit.

Reseller Level:


Admin Level:


Etag and custom cache time new

Previously, static static files, images, css files, js, files, etc... all used a default cache time of 65536. (18 hours)

Now, DA supports Etags, so the browser can ask DA if any file has changed, and DA will respond accordingly if it has or has not.

Also, those static files will still have a default cache time, but is now lowered to be 3600 seconds (1 hour) AND you can change this value in the directadmin.conf.

The internal default is:


so override that if you want it to last longer/shorter, which can be handy if you're developing a new skin, and don't want to hard refresh everything all the time.

mail_sni for dovecot and exim sni certificates new

This will replace both dovecot_sni and exim_sni, even though the functionality is roughly the same.

The dovecot_sni and exim_sni options will be deprecated from the directadmin.conf, and replaced with a single option:


which is the internal default.

To enable it, set:


and any certificate that is saved, either by pasting it through the SSL page, or created/renewed via LetsEncrypt, will trigger a write.

The /etc/virtual/snidomains file should already be setup and used as the "valid cert index", and will be used to setup the dovecot domain sni config.

Can also set:


in the to override domains that should not have it enabled.

Related: Ability to shut off dovecot_sni per-domain in the domains/

When a signed cert and cacert are found, the file is created (similar to with nginx), and then all records in the cert are added to:

/etc/virtual/snidomains - for exim to use as a lookup - if a subdomain exists in some other domain, but is also in this cert, the last one added has priority (would be a newer, valid cert anyway)

/etc/dovecot/conf/sni/ - with each record in there, pointing to the correct cert.

  1. OpenSSL and exim supporting SNI, usually CentOS 6 and higher.

  2. Recent dovecot and ./build dovecot_conf, for support of:



  1. CustomBuild 2.0 to install the exim and dovecot configs.

  2. secure_access_group=access should be enabled in the directadmin.conf, so that the certificates are chmod to 640 with group "access", so "mail" (within the access group) can read them.

cd /usr/local/directadmin
echo mail_sni=1 >> conf/directadmin.conf
service directadmin restart
cd custombuild
./build update
./build set eximconf yes
./build set eximconf_release 4.5
./build set dovecot_conf yes
./build exim_conf
./build dovecot_conf

DirectAdmin will only accept valid signed certificates. If you use a self-signed certificate, or your own domain does not exist in the certificate, then DA will refuse to accept it, and won't add the values to:


and will not create the dovecot sni file at:


If you rename your domain to, for example, the old values are removed from snidomains, and conf/sni/, and are only re-added if the above checks are still true.

Currently a certificate is only considered signed using the quick check where the Issuer and Subject values in the certificate must be different.

If you have a signed certificate which DA isn't accepting, please let us know, and include the certificate and ca bundle/chain so that we can check it out.


To generate snidomains file:

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

If you want to tell all live SSL domains to have their dovecot configs written, type;

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

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

this will recreate the sni/ for each SSL domain, plus one for the system hostname.

It will use the /etc/virtual/domainowners, to go through each domain, each cert, and remove any existing * entries from snidomains, and re-add whatever is present.

Ability to always load all php-fpm settings into User nginx.conf: nginx_fpm_always_set=1 new

New directadmin.conf option:


where 0 is the internal default, which can be set to 1:


and restart DA, to prevent the server{} entries from using:

include /usr/local/directadmin/data/users/username/nginx_php.conf;

and instead force the loading of the php-fpm settings right inside the nginx.conf for each domain.

This is already done in cases where Users have swapped php versions but this adds the option to do it all the time for other scenarios where you might need control tokens from the nginx_php.conf on a per-domain basis.

The nginx_php.conf is a per-User file, thus times where php is not swapped, but you need per-domain control, set this option to 1.


A change to how tokens are given to the php-fpm.conf template in per-domain mode (php versions swappped or nginx_fpm_always_set=1)

in that the previous tokens, although loaded in, where not sub-sorted.

For example, if you set:


in the |CUSTOM| token at Custom Httpd Config ->, the CUSTOM token is available to it, but the php-fpm.conf doesn't have it set anywhere, so the sub-tokenization never happened.

I've added a pre-tokenizer on all values to set:


for all tokens given to the php-fpm.conf

Note, a new token container is created for the php-fpm.conf template, so if you set something right inside the php-fpm.conf, it will not carry up and on to other templates for the domain (eg: nginx_server_sub.conf), but the CUSTOM tokens set in the GUI will, as they always have.

This was done to isolate these changes, as we don't want the sub-tokenization to happen too soon, as the order of tokens (eg: CUSTOM) is important, if a value is set there, vs CUSTOM1 before it.. the (eg: the USER should only be set once |CUSTOM| is called, so fred shouldn't be set until that point)

http2 for nginx: new token |SPACE_HTTP2| (TEMPLATES) new

If http2=1 is set in the directadmin.conf, a token will be present:

SPACE_HTTP2= http2

where the value is " http2" or "".

Note that there is a space before the http2 value, so that we don't end up with a space in the tempalte if http2 is turned off.





added |SPACE_HTTP2| after ssl, before the semicolon:

listen |IP|:|PORT_443| ssl|SPACE_HTTP2|;

If you're already using custom tempaltes, this won't affect anything.

Note, that the |MULTI_IP| token will now automatically insert http2 if http2=1 is enabled.

per-User template changes for http2 are not required for apache, it's enabled in a global config. to add $user_agent header new

If you're using the,, or, you can now access the environmental variable:


which is the User-Agent header passed to DA in the request, should you need to check that to validation, eg:

user_agent=Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36

New directadmin.conf option, where the internal default is:


such that existing installs will not have any behavior change.

New installs have:


in the template:


so NEW INSTALLS will have this feature enabled by default.

Old/existing installs are unaffected.

When you add:


any domain created with SSL enabled will have a private_html -> public_html link, instead of a private_html folder.

This should save some confusion, as the internet is slowing moving towards 100% https, so there is less need for split folders.

Of course, you can still set the private_html to be a folder or link via:

User Level -> Domain Setup -> -> private_html setup for


There are several different scenarios to take into consideration.

We'll assume default_private_html_link=1 for all of these scenarios (default_private_html_link=0 is the old behavior)

A) Merge method:

  • Empty User and domain are created normally first, then restore in a 2nd step.

  • or User already exists, then restore to merge from backup

For a merge, if private_html->public_html already exists, but the backup has a private_html folder, the private_html folder has priority.

The link is removed before the tar extraction, and the data is extracted normally.

If private_html is a folder, and the backup has a link, again, the backup has priority.

This case means if you had any data in your private_html folder, it will be deleted, and replaced with the link from the backup.

OLD DATA WILL BE LOST if you had any data in the private_html before the restore.

Either way, backups have priority.

After a backup has been extracted, if there is no private_html at all in the backup (no link, no folder), then then original private_html, and old value it pointed to, will be restored (in case it was pointing elsewhere).

BUT if the file in the backup file: backup/ has private_html_link=1, this causes an extra step to wipe the old private_html and replace it with a link (again, backup has priority, even if there was no link in the file).

So if you're doing something creative, where you don't want the domains/ in the backup at all, ensure private_html_link=0 is in the domain.conf file.

This probably doesn't apply to anyone.. as if it is set to 1, then the link would exist.. so might be a moot point.

B) restore creation

If the User does not exist prior to restore, then the User/domain will be created by DA at restore time.

With this case, the public_html/private_templates are not used, and backup has priority.

Related change: Skip all templates in ~/domains/ upon restore creation

option: spam_inbox_prefix_name=INBOX.spam new

You can now set a new value for INBOX.spam in the directadmin.conf.

This is the internal default:


to add that to the directadmin.conf and adjust as needed, if you wish to use a different value.

It's only used when spam_inbox_prefix=1 is set, which is when INBOX.spam applied.

Simply swaps all INBOX.spam strings with the new value.

The leading dot is added internally, where applicable (eg, Maildir paths), so don't add a leading dot, else you'll get 2.

UserDir for nginx in shared IPs (TEMPLATES) new

Previously, only the server IP could use /~username access with nginx.

This change lets you use the shared IPs for /~username access.



Also, this is controlled by the CustomBuild 2.0 option for:


but if DA can't figure it out (eg: it's not visibly set to no), it defaults to "yes".

Also make the apache UserDir settings enable/disable as well in the file:


based on the options.conf userdir_access setting.

If you don't need it on, we do recommend shutting it off, eg:

./build set userdir_access no

./build rewrite_confs


nginx_ips.conf - Added just below the index line:

include /etc/nginx/nginx-userdir.conf;

ips_virtual_host.conf - Added the if settings (was always present before)

UserDir public_html

https for redirect domain pointers (TEMPLATES) new

Added the 443 code for VirtualHost/servers in the templates:



added only if the current domain has SSL enabled.

Requires the User's httpd.conf or nginx.conf to be rewritten.

If you want this feature (After updating DA), you can do it for the whole server with:

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

Note, SSL certificates used in the VirtualHost/server will be the ones from the master domain the pointer is created under.

To avoid SSL Certificate errors, you'll need to ensure that the pointer is also in the master domains certificate.

LetsEncrypt can do this for you, just an extra checkbox from the list.

Ability to change pm.max_children in php-fpm.conf (TEMPLATE) new

Template change.



to the top of the template (before |CUSTOM1|) and change the line:

pm.max_children = 50

to be:

pm.max_children = |MAX_CHILDREN|

this will not allow you to go to (for example)


to add:


or any number you need, into the |CUSTOM1| token to increase the pm.max_children value.

Fill checkboxes in LetsEncrypt based on existing certificate new

If you make a change to a LetsEncrypt certificate by requesting a new one, this feature will select all checkboxes based on the values in the current LE certificate, saving the need to re-check values that are already in the old certificate.

named-checkzone to be more strict with -k fail new

New internal default directadmin.conf option:


Where this setting is used with the named-checkzone query -k option.

The default used before (via absense of -k option) was warn.

It was found that some warnings returned by named-checkzone would actually cause a full failure in named, so the strictness level of this call had to be increased.

Valid options for named_checkzone_level are:

  • fail
  • warn
  • ignore

If you find this to be too strict, set it back to level "warn" by adding:


to the directadmin.conf.

plugin_max_hooks to allow more plugins new

New internal directadmin.conf setting:


which repalces a hardcoded internal default.

Note that this never restricted the upper limit of plugins used, it did prevent the auto-filling of the blank plugin token values..

So if you had 20 tokens, and 8 plugins, the last 4 wouldn't be filled with "", and would end up showing "none".

So this variable simply sets the number of default plugin tokens to "".

Ability to disable forwarder pipes new

Ability to prevent processing email through email pipes.

Internal default is:


To disable it, add:


to your directadmin.conf file.

Periodically check to ensure versions.txt is being updated new

It's important to keep your server up to date. CustomBuild will help you update all of your services to their latest versions and config files.

You can either do this manually or have it check via cron, but in the event that perhaps you've forgotten to update it, or perhaps the update isn't working (say a files server has a stale copy),

this new feature will check to ensure that your file:


is no more than 30 days old.

It does this two ways:

  • login time, after a successful login to DA by any account, there is a 10% chance the check will trigger.

  • monthly reset will trigger the check each time.

New internal value:


where it's the percentage chance for any login to trigger the check.

If you change this check to 0, then the check will never run for either the post-login trigger nor the reset.

MySQL 5.7.6+ dropped mysql.user password column fixed


Although CustomBuild didn't get the MySQL 5.7 version added (because it had some conversion issues to MariaDB), we've also learnt that it dropped the password column completely:

basically replacing it with a column called "authentication_string", but also means there are other required syntax changes that DA will need to do if MySQL 5.7 would be supported in DA.

To support the 5.7.6 changes, add


to your directadmin.conf, and restart directadmin.

Update: April 2018: CustomBuild DOES currently support MySQL 5.7, so setting the variable in the directadmin.conf should happen automatically.

More info:

Backup dns: don't remove sole record if it's a linked IP fixed

Say you have a domain on, and you have linked to it for dns, DA will duplicate all A/AAAA records accordingly.

So you'd have:

www A
www A

however, some people might manually change the ns1/ns2 records to look like this:

ns1 A
ns2 A

while the other records are doubled up.

When DA creates the backup, it will remove the linkeds IPs from the zone, leaving just:

www A
ns1 A

where this would actually cause the write to fail the zone write check, since if this was present:  NS ns2

the ns2 A record is now missing.

This change is to check how many total A+AAAA records there are for that given name, and if there is only 1, then the value is not removed.

This can span AAAA records too, eg:

ns1 A
ns2 AAAA :::1:2:3:5

and DA will ensure if there is only 1 of either A or AAAA, that last value is not removed, even if it's only on the linked IP.

This will allow it to pass the zone check.

Backups will now also include the domains/, so that DA can eventually be coded to know that the value is an additional value, and should be swapped if the new server is using a different IP.

As this change currently sits, when you restore the zone, if you were to set the User/Domain to use, it would end up like:

www A
ns1 A
ns2 A

Since is considered a "custom" IP at this point, since DA does not yet check that it's a linked IP (as the backup system only currently supports a single IP to restore to)

If you're restoring to the same IPs, then it should be fine, it just gets a bit messy if you're restore to a different IP, then would need to be swapped accordingly after the restore.

More plugin hooks (SKINS) fixed

Added 4 more tokens for Admin, Reseller and User Levels, supporting up to 14 plugins.




Set language in dataskq for backup/restore messages fixed

When doing language translations, there is an area of code called the "InternalText" class. This is used to translated texts set within the DA binaries, and not part of the html skin code.

When browsing in DA through port 2222, the language set set from the user.conf files, so DA knows which internal texts to use.

However, for dataskq task.queue commands, there is no login, so the language is not set, so some background messages might be in English, this is the bug.

Solution for the backup/restore is to set the docsroot and lang settings from the user.conf as needed, based on who called the backup/restore.

For the "Email Disk Usage" count, DA includes various areas, including:



which are files managed by Dovecot.

Dovecot often uses hard-links when copying messages around to save space, but when DA previously counted the disk usage, the same file might get counted many times.

This changes is to still count the same file multiple times, but if it's owned by the User and has multiple hard links, divide that file's current size by the number of hard links.

So if a file has 2 links, DA counts 1/2 the size twice, giving a more accurate result.

Note that this will not affect the total disk usage, which relies on the System Quotas, a completely different system.

This change will just make the results closer to the "du" output, which handles multiple hard-links in a different way (likely tracking all of their IDs in a hash)

but should arrive at the same result as long as the files are under the same path (which is why this is only done for specific dovecot paths)

This will also not affect the File Manager directory usage output for the above folders, so for example, the results you'll see for /home/user/imap will probably be different than the total that DA gets during the tally.

Removing additonal IP from User that was in use by a domain causes wrong IP in config fixed


  1. User + domain assigned to server IP .1

  2. Add an additional IP .2 to the User.

  3. Login as the User, add .2 to the domain.

  4. Remove .1 from the domain.

  5. logout, back to creator, edit user and delete .2 from User.

Expected result would be everything, dns, all configs, revert back to .1.

What was happening, was close, but the file did not get the last .1 user IP.

Previous check was checking if the's IP was not in the file, and use index[0] for the .conf ip,

but the was actually empty at that point, so the conf overwrite did nothing, and left the previous .2 IP.

New check to ensure the "backup" IP used is not NULL, and if it is, use the user.conf ip.

swap gethostbyname with getaddrinfo fixed

When DA used to do lookups, it would use the older gethostbyname, which still worked fine.

However, to better support IPv6, I've switched over to getaddrinfo (which is newer anyway)

So any lookups that DA does, will now support IPv6.

However, DA still favors IPv4, so the hint will start with AF_INET, and only move to AF_INET6 if there are no IPv4s.

LetsEncrypt: remove deleted domains/subdomains from san_config before renewal fixed

If you've got subdomains, create a multi-domain LetsEncrypt certificate to include those subdomains, and then delete the subdomain, the renewal would fail, because the san_config still has the subdomain listed.

Before any renewal call to, DA will scan the list of subjectAltName=DNS entries, to ensure that:

  • they're a domain on the sysetm

  • or a subdomain of a domain on the system

  • or resolve to any IP that exists on the system (data/admin/ips/ip.list)

If not, it's removed from the, prior to the renewal via

Deleting a User with php-fpm while a php script is running throws error fixed

If you run php-fpm and the User's website is active when you delete it, this error may appear:

Unable to remove Unix User. Continuing with the other files.

userdel: user testuser is currently used by process 123456

DirectAdmin does scan for, and kill all Users processes before running the userdel command, however php-fpm is quick to spawn a new process for the User.

The solution is to delete the file:


and quickly triggering a graceful reload of php-fpm before running userdel.

Currently, this is triggered multiple times if multiple Users are deleted, so we may need to change it.

Predicting the full list of Users is possible, but more difficult due to the recursive nature of Reseller, so for now the graceful trigger is done per-User being deleted.

As "graceful" restarts don't affect live requests, in theory, this should't hurt other Users, except those being removed.

Note, by "graceful", it will be whatever is the correct term for your OS, as it has several variables.

If "php_fpm_restarts=1" set, the "restart" is used.

if systemd, then reload is used,

else graceful is used.

CMD_SYSTEM_INFO not showing nginx for nginx_proxy fixed

Fixed a few things in the CMD_SYSTEM_INFO area, mainly the missing nginx entry when the nginx_proxy is in use (both nginx apache are running)

Also enabled json=yes.

Double mysql_close call causes segfault fixed

Bug that has oddly only showed up on Debian 9, caused by 2 calls to mysql_close on the same pointer during a restore.

Main surprise is that other systems did not trigger a segfault. Found/fixed either way.

FreeBSD: apache_public_html not setting apache group fixed

The older apache_public_html=1 setting (Which is redudnant with secure_access_group, not no longer enabled by default) was not correctly setting the public_html folder to apache.

Linux is not affected, as the process group is applied to the folder, but apparently not with FreeBSD.

FileManager Edit: write to temp first fixed

When saving an edit file, changed to save to a mkstemp file first, before renaming the successfully saved file to the original name.

This prevents files becoming 0 bytes if you save an edit to a full disk or full quota.

However, this also means that symbolic links will no longer be followed, if you editing a file through a link.

So if you were, be sure to edit the destination file, instead of the link.

Debian 7 cast bug for php-fpm versions fixed

Likely an old compiler bug or something similar, where a double value of 71.0 cast to to (int) gives 70 (via options.conf php2_release=7.1, x10)

Solution (not great) is to add 0.00001 to account for the floating point rounding error, where the int always rounds down, so it will then end up with the correct 71.

Other OS versions don't seem to run into this rounding error.

Related to php-fpm config files, with Custom Httpd Config, eg:




it was writing to the fpm70 file, when it should have been fpm71.

segfault (SECURITY) CVE-2017-18045 fixed

Important security fix where a segfault from a specific request could allow a remote attacker unauthorized access.

For anyone who cannot update to this version of DirectAdmin (eg: end-of-life OS), please add:


to your directadmin.conf and restart DirectAdmin.

We won't be immediately commenting on the details of the bug to allow everyone time to update.


As some client have disabled their auto-update or have still not updated yet, to help get the message out more quickly, we've requested a CVE ID number:



"Could not save new password. The password changing feature has been disabled"

Last Updated: