Version 1.55.0

Released: 2018-12-07

mail_sni=1 enabled by default for new installs new

Relating to the dovecot/exim mail_sni feature;

mail_sni for dovecot and exim sni certificates

This feature will now be enabled by default for new installs.

This will be done by having:


present by default in the data/templates/directadmin.conf.

As the internal default will still be:


If you don't have it set in your directadmin.conf, it will be still be off.

Existing installs won't be affected by the template change, since it's only used for new installs.

It can still be enabled normally via id=2019.

Optional: check_referer_port=0 new

New directadmin.conf setting:


with the internal default value as 1.

When disabled, by adding this to the directadmin.conf:


DirectAdmin will not check to ensure the ports match during a request.

The main use for this would be during a proxy request, where the ports may not match, but you still want other referer checks, like the host, to be done.

Note, if you use:


then DA will automatically set:


just to avoid any confusion that may cause one to think the port check is still being done with check_referer=0, which won't be true.

If check_referer=0 is set, then no referer checks are done at all, regardless of the check_referer_port setting.

CMD_CHANGE_INFO: set multiple values at the same time new

New option with the:


call that allows you to set multiple values at the same time.

First, include this in the request:


which then lets you set more than 1 value.

If any are no included, that's fine, it will only set what's passed.



(session=yes) optional to set it only in the session file, not user.conf




If there are any errors along the way, it may abort prematurely, or may continue, depending on the error.

Basically, if you get an error, don't assume anything was done.

If any error did happen, the will not be called, even if some values might have been set.

So if you get errors, ensure you inform the User clearly they it might be wise to then run it again, but on a per-item basis again,

to ensure everything is set, and the is correctly called for each of those items.

Evolution: Option for Reseller config.json to not be inherited from Admin new

In the sake of branding, some Resellers might not want to see the customizations set by the Admin.

Customization in evolution are stored in a config.json file, per Reseller/Admin account.

If the current account does not have one, its "creator" is queried to see if they have one.

In this case, a Reseller's creator is often "admin", thus if the current Reseller does not have a config.json customization override (which they won't, by default), the admin's config.json is shown.

This is not always desired.

New option, internal default:


which can be disabled by adding:


to the directadmin.conf


by adding:


to the Reseller's file:


The reseller.conf trumps the directadmin.conf.

When DA reads it as 0, then should the current Reseller not have a config.json, the 'creator' config.json is not searched for, and all skins defaults are used.

Although it's not a standard package/account option, you can pass:


with Reseller creation:


or modification:


The yes|no reseller.conf, vs 1|0 directadmin.conf are just in case we do ever add it to packages, then it will be correctly supported.

JSON to list commands.allow/deny new

Evo needs the ability to know which commands shouldn't be allowed before they're shown/used.

If there are commands.deny or commands.allow in effect for the current login (being per-user, global or login key), the restrictions/allowances will be included in:


You'll always see this option:


and if yes, then more info will be provided in:



"has_cmd_overrides": "yes"

Just as an example, where this would block all commands, except the listed commands.allow.

Note, only 1 of commands.allow/commands.deny might be set with has_cmd_overrides, if only one of the 2 files exists.

The commands.deny trumps commands.allow, so in your logic, check that first to decide if a command is blocked.

If it's not denied, if the commands.allow exists, then the command MUST exist in that file.

If commands.allow is set, but empty, all commands are blocked (and you wouldn't be able to view CMD_JSON_LANG anyway)

So if you have commands in command.deny, but commands.allow is not set at all, then all commands are allowed, except the values in commands.deny.

If commands.allow is set, but commands.deny is not set, then only the values in commands.allow are allowed.

If both are set, commands.deny wins over commands.allow.

Note, the bulk commands.* options:




are removed and converted into the full list of values, so you don't need to worry about knowing which command belongs to which level.

These 3 ALL_* values won't be listed.

Just check the CMD_ you'r looking at against these lists, if they're set.

Use TXT DNS for current DA version new

In an attempt to be more efficient at allowing all DA boxes to know which version is the most recent, we've added a new TXT record: 14400 IN TXT "v=1.54.1&f=1.541000"

where DA will try that first, before attempting to connect to /ver.php.

This will be far more efficient, since a lookup has to be done anyway to determine the A record for, before doing the high-overhead apache connection to check it.

So we just skip that A lookup, and instead do a TXT lookup on, giving the version all in one clean hit.

This has the added benefit of caching the values in your /etc/resolv.conf name-servers, so future hits won't even touch

Note for systems where DA previously connection "bind" to a proper IP (say the lan_ip value wasn't set if you're on a LAN), it used to show:

unable to connect

For the version in Admin Level -> Licenses/Updates

with this change, unless there is a DNS value, you won't be able to use the "unable to connect" version to determine if your lan_ip is setup incorrectly or not.

Prevent Reseller from customizing/branding skin new

New internal value, with default:


where, if you set this to 0, Resellers will not be able to access the skin customization page, nor save customizations.

Admins are not affected by this setting.

SSH Keys Management (SKINS) new

New User command:



Ability to manage ssh keys via User Level, and by Admins/Resellers Levels.

This command is not domain dependent.

More info to come.


  • Ability to ssh to other servers without needing a password.

  • Ability to allow others to ssh to your account, without giving them a password (they give you their key)

  • Possibly cronjobs for rsync to push files to remote accounts seamlessly.



id=id  (any name you want to give it for or id=test for
(passwd=secretpass OR blank/empty for no password on the key, useful for script automation.)
keysize=1024|2048|4096 (default selected 2048)

Note if you use name=id, this will be what's used for outbound ssh connections.

The remote server would need the file on their end.

For the type, use rsa. The other option of dsa is dropped with OpenSSH 7.0+ so DA won't create it but will show it if exists, and rsa1 also not supported all.


ability for a Reseller to add a key and apply it to all of their Users.

Option to also apply it to all future Users, when created.

May ignore the "ssh" option for Users, adding the key regardless.

The key would not work until ssh is enabled for the given account (shell needs to be set by DA, and AllowUsers set by DA in sshd_config for that User)

Similarly, Admins added to all Resellers/Users created by them... perhaps an option for every single account on the box.

The global list is stored at:


with one key per line, eg:


where "who=all" means it's added to all Users below this Reseller, and will be added to all Users when they're created.

Similarly, who=selected means only the selected Users are affected. Newly created Users would not get this key.

Lastly, who=except would mean all Users except the selected Users. Newly created Users would get this key.

to set a current file to be globa, use:

(select0=fred, select1=george)

This will add the given pub info to the ~/.ssh/authorized_keys, either from an existing, or pasted value.



text=ssh-rsa AADATAQ==



Regarding the option0, option1, they are optional.

They specify the key's options such as IP or command restrctions. See man sshd(8) for info on the options/values.

Note the %3D is the encoding of = and %26 is the encoding of &. The option0 entries are double-encoded in the final post string.

The options that do not have values must not have values set, and options that DO have values MUST have values set (again, 'man sshd')



or (used by Enhanced)


where type=authorized_keys, removed the entry from the:


and type=public removes the 2 files:



the type=public has an option value (check box, enabled by default):


it will also delete the keys with that matching fingerprint from the authorized_keys file.

Note that newer OS's will show a different SHA fingerprint format.

This doesn't change anything as it's only used as an index.



with optional &fingerprint=07:af:73🇩🇪8f🆎59:d3:f7:49:50:72:c1:48:51:1d

to only show this global_keys entry.

By default, you should use enabled_users=no, for a faster main page load for Resellers+.


            "comment": "",
            "data": "AAAAB3NzaC1...JJw==",
            "fingerprint": "07:af:73:de:8f:ab:59:d3:f7:49:50:72:c1:48:51:1d",
            "keysize": "2048",
            "type": "ssh-rsa"
            "comment": "",
            "data": "AAAAB3NzaC1...fsw==",
            "fingerprint": "ec:b3:44:98:6e:f9:b3:23:70:7c:87:04:3f:af:30:45",
            "keysize": "2048",
            "type": "ssh-rsa"
                    "enabled": "yes"
                    "enabled": "yes"
                    "enabled": "yes"
            "who": "all"
        "command": "value",
        "environment": "value",
        "from": "value",
        "no-X11-forwarding": "checkbox",
        "no-agent-forwarding": "checkbox",
        "no-port-forwarding": "checkbox",
        "no-pty": "checkbox",
        "permitopen": "value",
        "tunnel": "value"
            "text": "1024",
            "value": "1024"
            "selected": "yes",
            "text": "2048",
            "value": "2048"
            "text": "4096",
            "value": "4096"
            "comment": "",
            "data": "AAAAB3NzaC1...fsw==",
            "fingerprint": "ec:b3:44:98:6e:f9:b3:23:70:7c:87:04:3f:af:30:45",
            "keysize": "2048",
            "timestamp": "1539761813",
            "type": "ssh-rsa"
            "comment": "",
            "data": "AAAAB3NzaC1...Kow==",
            "fingerprint": "ab:d6:60:d3:e8:75:c0:82:de:52:91:61:aa:22:e7:c9",
            "keysize": "2048",
            "timestamp": "1539761558",
            "type": "ssh-rsa"
            "comment": "",
            "data": "AAAAB3NzaC1...JJw==",
            "fingerprint": "07:af:73:de:8f:ab:59:d3:f7:49:50:72:c1:48:51:1d",
            "keysize": "2048",
            "timestamp": "1540022670",
            "type": "ssh-rsa"
            "comment": "",
            "data": "AAAAB3NzaC1...BHw==",
            "fingerprint": "59:09:0c:a1:f5:69:12:89:72:79:ed:45:11:1b:f4:3d",
            "keysize": "2048",
            "timestamp": "1539757117",
            "type": "ssh-rsa"


If a Reseller or Admin, the json will include a top-level array global_keys, with each key that is set to be global, with info.

As well as "users", a list of all current Users under this account (just a dump of the users.list file)


    "users":  \[ "fred", "george" \]

The call made by a normal User will not have the global_keys nor users array.


new files:








added new line:

|*if USERSSH="ON"|
<a href="/CMD_SSH_KEYS">SSH Keys</a><br>

CMD_CUSTOM_HTTPD: JSON to show virtual_host2.conf.pre, virtual_host2.conf.CUSTOM.pre new

On the page:


Within the "VH1" to "VH6" arrays, if there are custom values, a sub-array will be shown, called:




        "custom": "1",
            "/usr/local/directadmin/data/templates/custom/virtual_host2.conf.CUSTOM.4.pre": "#virtual_host2.conf.CUSTOM.4.pre\",
            "/usr/local/directadmin/data/templates/custom/": "\",
            "/usr/local/directadmin/data/templates/custom/virtual_host2.conf.pre": "#THIS IS PRE TEXT!!\\"

Also applies to nginx template, where applicable.

Note, the cust_httpd.pre custom files are listed in json, mentioned in id=2158.

UTF-8 Encoded To/From/Reply-To in DA emails new

Should a DirectAdmin User's "name" contain UTF-8 characters, the To, From, Reply-To headres can now be encoded with UTF-8.

To enable this, add:


to the directadmin.conf and restart directadmin.

Cron: default to MAILTO="" for new accounts new

The previous default for new accounts is to not set the MAILTO= value at all in the crontab.

This defaults to sending email to that User if the cron generates any output.

The issue with this is Users often never check their system email accounts,typically flooding it with more messages than the directory can efficiently manage, as well as causing issues with the backup, since DA/tar cannot easily read the contents of the oversized Maildir.

The solution is to default new accounts to use:


in both the crontab.conf and the actual crontab (for direct_crons=1, which is the current default).

Existing accounts will not be affected.

If you'd like, you can change the cron email to a blank value in a given User's cron page, and save, to prevent any further emails.

extra unzip command line options & max extraction file listing count new

New directadmin.conf setting, with internal default as blank:


The usaul way DA unzips a file is

unzip -qo '/domains/'

In some cases, this error could show up:

path/blaÃŒÅtest.jpg: mismatching "local" filename (path/bla├М┼аtest.jpg), continuing with "central" filename version

The solution is to add these options to the directadmin.conf:

extra_unzip_option=-O cp396

and restart directadmin.

The value is inserted after the -qo, so it would end up looking like this:

unzip -qo -O cp396 '/domains/'


Also added into the default internal settings is:


where 5000 is the internal default.

You can override it by adding it and the new value to the directadmin.conf.

When extracting a tar.gz or a zip file, if the number of files within the compress file is greater than 5000, then the rest will be chopped of.

DA basically just looks for the 5000'th newline character and nulls it with , ending the string.

If this is hit, this string is added to the end of the listing:

Maximum number of files listed (5000). Suppressing further output.

This should prevent hangups if a very large zip/tar.gz is being extracted.

hook scripts for Message System new

Hook scripts that can be added to:


to be triggered anytime DA sends a notice to the Message System.

You can exit with a non-zero result for any * script (if they exist) to suppress the message from being sent.

The * scripts don't check the exit status.

The output is read in, but not used, so if you need to save anything, log it from within the script,

or use the task.queue to create another message to whoever needs it.


Triggered for any event that sends a message to all Admins.

subject=subject line
message=message body
email_only=0|1  (1 means it will only trigger an email in some cases when set, like BFM message suppression, without a Message System notice)

You can get the list of admins from


This does make a call to "sendSystemMessage", which also has a hook, if email_only=0.

same data is, but also:

include_admins=0|1 - where 1 means the Admins will also be notified, but won't be in the "users" list variable. See the admins.list for the list of Admins.

alternate_email=0|1 - usually 0, but some cases like BFM message suppression means the user.conf alternate_email is used, so main inbox isn't flooded for common messages.

This is always 1 for those cases, but only matters if the user.conf actually has alternate_email set. If it's not in the user.conf, it makes no difference.

Refers to the emails used for notification of the message in the Message System.

users=user0=fred&user1=george - where the value to the right of "users" is URL encoded (the example doesn't the encoding of & and =). It's possible this value is blank if include_admins=1.

Services Monitor to use boot scripts as backup method to get parent PID new

In some cases, you might have a service in the services.status file, where there running binary does not match the boot name.

As a result, DA might not see it running, so monitoring work.

This also applies to spamsasssin (spamd) on FreeBSD, which shows "perl" as the binary.

So the behavior will be, should the normal PID retrival method return zero PIDs, this will be the fallback to figure out if the above scenario is true (only for services.status items), and whatever it could figure out, to the list.

For systemd, the Master PID is parsed right out of the "systemctl status httpd" (for example), only to get the master PID number.

For init.d systems, it call /etc/init.d/httpd status, and strips the first pid number from there. DA will look within ( ) brackets for the PID numbers, eg: "status (pid 82103) is running..."

Once the master PID is found, DA then uses it's own internal tools to find the child PIDs below it (varies per OS type)


  • Services Monitor

  • System Information

  • dataskq services running check

Based on extra entries added into:


Also, the "Services Monitor" also now fetchs ram usage, but using the main pid from the status, and calling:

ps o comm,rss -C -p  12345

Packages: rename (SKINS) new

When editing a package, changing the name at the bottom is considered a "save-as" action, so you'd end up with a new package, and the old package is left unchanged.

Users with the old package are also unchanged, as they're not using the newly saved package name.

New option, a "Rename" checkbox at the bottom left of the package edit page, for both User/Reseller packages,

where, if checked, DA will fully remove the old package, and also change all users with that old package name to use the new package name and it's values/settings.





new checkbox in the bottom td:

|*if NEW_PACKAGE!="yes"|<input type='hidden' name='old_packagename' value='|PACKAGENAME|'><span class=float_left><input type=checkbox name='rename' value='yes'>|LANG_RENAME|</span>|*endif|


new class:

.float_left {
    float: left;

named_service_override=bind9 new

On some OSs for named/bind, it's simpler to have DA use some different script name, rather than trying to force the specific boot script names.

Specifically on Debian, apt-get provides bind9.service, but DA would still be looking for named.service.

New feature, internal default is:


where it's unset.

To have DA call bind9.service, add this to the directadmin.conf file:


Note, if you add named_service_override to the directadmin.conf, ensure it has a value.

If it's present but blank, this means DA would call:

systemctl reload .service

instead of:

systemctl reload bind9.service

Possibility to set this as the default for new Debian 9 boxes, if that script is present.

force_ssl for easy apache/nginx redirection to https (TEMPLATES)(SKINS) new

New checkbox in both:

User Level -> Domain Setup ->

under the "private_html setup for" table, showing:

[ ] Force SSL with https redirect

and at bottom of this page:

User Level -> -> SSL Certificates

where, if checked, the port 80 VirtualHost/server{} entries for a domain/subdomains will automatically redirect to the same host and request, but with https.


New setting will be:


in the:


if it's turned on.

If it's off, it will be absent from the file (will not show force_ssl=no)



will now include the:

"force_ssl": "yes",

if turned on.

If off, it will be absent from the output.



In the "private_html setup" table, below the 2 radio tr entries for the link, add a new row:

<tr><td class=list2><input type="checkbox" name="force_ssl" value="yes" |SSLDISABLED| |FORCE_SSL_CHECKED|></td><td class=list2>|LANG_FORCE_SSL_REDIRECT|</td></tr>


<table class=mb15 cellpadding=3 cellspacing=1>
<form name=https action="/CMD_DOMAIN" method="post">
<input type=hidden name=action value="private_html">
<input type=hidden name=domain value="|DOMAIN|">
<tr><td><input type="checkbox" name="force_ssl" value="yes" |FORCE_SSL_CHECKED|></td>
<td><input type=submit value='|LANG_SAVE|'></td>

where it uses the same action=private_html form.. but really has nothing to do with the symbolic link, since both radio buttons are excluded from the form.



/usr/local/directadmin/data/templates/ --

added token:


which, if set in the Domain Setup for that domain, will be filled with either:

SetEnvIf X-Forwarded-Proto \\"https\\" HTTPS=on
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !https \[NC\]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} \[L,R=301\]


if ($http_x_forwarded_proto != 'https') {
    eturn 301 https://$host$request_uri;

Crons: pad index with zeros to maintain order new



the "id" values for each cronjob are integers, but if you had 10, for example the order would be:






so the order could be confusing.

As there are non-integer indexes in this array (eg: MAILTO) we could not simply convert the search/sort to be an integer sort/search.

Instead, we've padded the indexes with zeros, so it would now be:






so that the order is maintained.

The direct_crons feature generates the IDs on the fly, so that's not an issue (nothing would change there),

and the per-User crontab.conf file will not be touched until saved.

All reads done after this change will convert 1, 2, 3 to id 001, 002, 003 on the fly.

So just be sure you're using the "id" tags from the current read before passing it to edit/delete.

The id value passed back to DA must match the padded value, so if you see id=001 you must pass id=001 for it to be seen.

But if you've coded things correctly where you're not concerned with the indexes being integers, and treat them as strings, then likely no changes should be required to your API calls.

Custom hook: new

New hook script, called after the has finished running, and was successful.

Create script:


With environmental variables:

Use exit 0 for success, and exit with a non-zero for error,

Although the result will be added to the output regardless, but only shown if there is an error.

Proftpd.conf: updated TLS protocol/ciphers (TEMPLATES) new

New /etc/proftpd.conf default changes.

It now uses these ciphers/protocols:

TLSProtocol TLSv1 TLSv1.1 TLSv1.2

This is both in the file:


as well as CustomBuild 2.0 configure/proftpd.conf/proftpd.conf, if ./build proftpd is run.

optional template: plugin_iframe.html (TEMPLATES) new

Not present by default, but used with plugin feature:


which current hard-codes the iframe html output around the |OUTPUT| token.

This new optional template, if created, would be used in place of the hardcoded html.

Main purposes is to make debugging easier.. shouldn't be needed for most cases.



<html><head><base target="_parent" />
<meta charset="utf-8" />
</head><body><div id="iframe-container">

LetsEncrypt: max failed renewal attempts before sending failure notice new

New directadmin.conf option, internal default:


Because the renewal attempts start at (by default) 60 days before the 90 day expiry, this is 30 days grace before the SSL certificate actually becomes invalid.

As a result, we do have time to retry renewals before we need to be concerned about a failure.

This feature allows for up to 5 renewal attempts over 5 days before actually notifying the User, should the renewal fail a 5th time.

Often times, a renewal may fail the first time but succeed later on, so this allows a few more attempts before actually notifying the User.

After 5 attempts, if there is still a renewal failure, it will only then message the User.

It will continue to notify the renewal failures until the certificate has actually expired, at which point no more renewals will happen (same behavior as before)

Custom hook script: post restore but pre-cleanup new

New custom script:


which is called after the restore of a User account, but just before the path is cleaned up.

This is called just before, which is triggered after the temporary extracted data is cleaned up.

The same variables are passed, as to the

$reseller   (user.conf creator)

Debug mode modulus %10==7 to show timestamp new

Sometimes you want to know exactly when things happen, when using DA's debug mode.

This change adds a debug%10 == 7 check to the debug level.

Basically, if you end any debug level with a 7, then the debug output line will start with the current timestamp of the system (handy for cron debug logging, for example)

DEPRECATED: User or mail can get their dovecot quotas with directadmin suid mode new


Drop directadmin suid bit support


Not likely a feature for most people (at the moment), but if you set:

echo 1 > /root/.suid_directadmin

and set permission:

./directadmin p

it will set DA to be:

-rwsr-xr-x 1 root diradmin

This is used for per-User calls.. at the moment, just two options (existing: ./directadmin --DocumentRoot) but now it also adds:


which can be called as root, mail or a User.

If called as a User, it will ensure that belongs to the UID caller (not likely a common usage, but is an option since suid was needed anyway for 'mail')

If root or mail, it will work for any email that exists the system.

If the email is valid, dovecot quotas work, then it will output something like:


which would be in KB, so the above example, the account is using about 20Meg of 100Meg.

You MUST check the exit code of the directadmin call.

If it's not 0, then ignore this directadmin result.

If the account in question has unlimited dovecot quotas, then the output will be:


This feature uses the doveadm quota call to get the info, as dovecot is going to be what will accept/reject the email anyway.

The purpose of this feature is to allow exim the ability to block an inbound email at smtp-time via ACL, rather than accepting the message, and having it bounce after the dovecot lmtp delivery rejects it.

The ACL in question does not yet exist. If it will exist, it wouldn't be for a while after this version of DirectAdmin have been released.

Domain Pointers: option to not redirect to www (SKINS)(TEMPLATES) new

UPDATE with 1.57.0: Deprecated

Replaced with force_redirect:

www vs no www domain/pointer redirection (SKIN)(TEMPLATES)


NOTE: This feature's implementation may change for 1.55.1, for a different design.

As this decision was made after RC1 was pushed, we'll leave this feature as-is for now.

If you're a skin designer, you may want to hold off until 1.55.1 before add the changes, since the variable names/tokens may be removed.




The redirect type of domain pointed used to always redirect to the form, without any option to redirect to just


A new checkbox on CMD_ADDITIONAL_DOMAINS (to be posted to CMD_DOMAIN) to let the User decide if domain pointers should redirect to or just




New JS to hide/show "www." based on the new checkbox on CMD_DOMAIN_POINTERS

The action=modify on CMD_ADDITIONAL_DOMAINS form now also had:


which is needed in order to use the:


checkbox. If it's not passed, then with form_version=1.1, it's assumed to be off.

Without form_version>=1.1, you can pass either www_pointers=yes or www_pointers=no and DA will pick up the value, as it's explicitly specified.




added to top section of both:

|*if WWW_REDIRECT="no"|

and lower down (both again), changed the:


redirect text to be:



new default:


which can be set to no via the CMD_ADDITIONAL_DOMAINS Domain Setup page.

The domain pointers file at:


will now have it's format changed from:


where a late design change mean the is not actually used at all...

BUT since we will retain the conversion to:

you can add www=yes|no manually to the pointers file, if you want to override the setting.

DA won't add www= to the pointer config.

Domain Pointers to have local/remote email control new

Previously, the Domain Pointers always followed the mail domain in terms of if they had local or remote email ("Local Mailserver" setting on MX Records page).

DA will still flip pointers to match whatever the main domain is set to.

However, now you can change Domain Pointers to remote/local independently after the main domain has been set however you wish.

The CMD_DOMAIN_POINTER? lower table will now have a new column called "Local Mail"

and 2 new buttons will appear at the bottom to the left of the "Delete" button:

Local Mail

Remote Mail

The behavior of the form to control these values will be:



To set the select# list to be local, add:


To set the select# list to be remote, add:


If you forget to add local_mail or remote_mail, these pointers will be deleted 😃

So ensure you're not using a new skin with these buttons with an older version of DirectAdmin.

For Enhanced, it's not an issues because it's a hardcoded table+buttons that only shows up with this new version. new

All emails generated by DirectAdmin (eg: Message System notices, etc) are pushed through:


This custom hook:


which can be created if you need it,

will allow you to override the sendmail call and handle the message yourself.

For example if you want to use a remote smtp-server directly, for all DA messages, you could setup some sort of wrapper in the to deliver them to some port 25.


If the script exists, the return code will determine how DA will handle the following actions:

0 - if exit 0 is used, it's assumed the has accepted the message, and there are no errors. Sendmail will not be called. You can also use this to no have DA do anything, even if the script does nothing.

1 - For exit code 1, DA assumes there was an error, and the called DA function will return with an error code (caller depends on what message is shown, etc)

2 - Exit code 2 will mean the script intends for DA to ignore that the script was there at all, and continue normally to a sendmail delivery.


full_message = all headers and message contents
message = just the body of the message
to = the "To:" header. Could be normal email, or utf-8 nencoded email if utf8_encode_from_to=1 is set in directadmin.conf.
subject = the "Subject:" header.  Could be utf-8 encoded if utf8_encode_subject=1 is set in directadmin.conf.

OPTIONAL (possibly not be passed)

from = the "From:" header. Could be normal email, or utf-8 encoded email if utf8_encode_from_to=1 is set in directadmin.conf.
reply_to = the "From:" header. Could be normal email, or utf-8 encoded email if utf8_encode_from_to=1 is set in directadmin.conf.
headers = url-encoded string of possible extra headers, eg: "MIME-Version=1.0&Content-Type=text/html" (for the <html> as start of body case,, for example.

per-User/per-Reseller send_usage_message override new

Relating to feature:

option to not send warning emails for bandwidth/quotas

The value:


can be added to a given User's user.conf and/or a given Reller's reseller.conf.

The value set in these files will override the directadmin.conf send_usage_message option.

This can be used to enable/disable the notices for a given account when they approach their limit.

The reseller.conf override would apply to a Reseller's limits, and the user.conf to the User's limits.


No skin changes are required for Enhanced, it's in the hardcoded token.

The setting can be changed by Users at:

User Level -> Site Summary / Statistics / Logs (CMD_USER_STATS) -> Limit Notice

where it gives 3 options in the select box:

Default (On) or Default (Off) (pending the directadmin.conf send_usage_message), with value 2.

On value 1

Off value 0

If Default (On/Off) is selected, the user.conf does not have send_usage_message.

Else, the On/Off is set based on the user.conf send_usage_message.

Same idea for Reseller, except:


and reseller.conf.



method: POST

send_usage_message=<any text>

with standard dynamic output.

For Resellers, it's the same command, the only difference is the button name:

send_usage_message_reseller=<any text>


the "stats" array has added entry:

    "setting": "send_usage_message",
            "selected": "yes",
            "text": "Default (On)",
            "value": "2"
            "text": "On",
            "value": "1"
            "text": "Off",
            "value": "0"
    "max_usage": "1"

where most should be self-explanitory, but the "max_usage" will be the internal/directadmin.conf setting for send_usage_message, in case you need to know it.. for some reason.

The "Default (On/Off)" already represents that value, but in translatable text, thus this is a more definitive way of knowing the value.


Similar, but because the arrays for Reseller stats are different, this is a new array at the top level:

    "max_usage": "1",
            "selected": "yes",
            "text": "Default (On)",
            "value": "2"
            "text": "On",
            "value": "1"
            "text": "Off",
            "value": "0"

Php selector to only show 1 selectbox (SKINS) new

CustomBuild 2.0 has the ability to support more than 2 php versions quite easily.

The hold-back is the internal DirectAdmin code to manage php1 and php2 versions in the configs and templates.

Also, the need for 2 php versions at the same time for one website seems very rare (we've not seen it in use).

The solution is then to simplify the Php Selector in the Domain Setup page for a given domain to only show 1 php selector, instead of 2.

This single selector will still given the User the option to select any php version that CustomBuild supports (still just 2 at the time of this writing, but more once we get it sorted out)

We'll do this in small steps in case our above assumptions are incorrect.

The internal default, which is already set it:


this was previously intended to be an on/off switch for the selector.

This default value will remain unchanged, but it's functionality expaneded to be:

php_version_selector=0 : off
php_version_selector=1 : show only 1 php selector (.php files can be any version selected) DEFAULT
php_version_selector=2 : show the previous 2 php versions.

So by default, Users will only see 1 php selector with the php_version_selector=1 value in their Domain Setup page.

If you are using this feature where you might need .php and .php72 for one website at the same time (which we've not seen), then you can set:


and please let us know.. as the current plan is to remove the 2nd php version instance from the templates, since it's likely not needed, and very rarely used.

If this all goes ahead, then CustomBuild 2.0 will then be changed to support php3_ php4_ etc, up to N version (any number), likely for php-fpm (mod_php can only be used once)

More DA changes will be needed for that later one, so this is more of a feeler for the proposed changes.

If may php DO need the 2nd php selector, then the other option is to leave it there, but still allow CB2 to have php3+.. and only allow a domain to use 2 different versions at the same time (we prefer to no go this route).



if directadmin.conf php_version_selector > 0



if json var "has_php_selector" == "yes", then





ftp extended=yes was not showing suspension fixed

Bug with new feature:

CMD_API_FTP extended=yes|no

not correctly showing suspended=yes


allow header: X-Json=yes fixed

There are cases where DA does not yet have the request parsed yet, but it needs to abort.

The usual case would be a timeout, for example, during a very large file upload.

In these cases, DA has not yet parsed the GET/POST body data yet, thus has no idea if the script wanted json=yes being set.

This optional value lets you get a more reliable output, by allowing the new header:

X-Json: yes

to be added to the request, as the headers are parsed first.

Other cases are currently if the IP is blacklisted and this header is set, it will now return a dynamic style output with error=1.

With the header set, the optional template:


will NOT be used, since the output shouldn't be html.

If other scenarios are found that are not generating json when the header is set, let us know and we can include them into the correct output handling.


Extra headers:

In addition, if a timeout occurs, a header will be added:

X-DirectAdmin: timeout

or if a segfault occurs:

X-DirectAdmin: segfault

Sample timeout response from:


Connection: close
Content-Type: application/json; charset=utf-8
Server: DirectAdmin Daemon v1.54.1 Registered to
Set-Cookie: session=gf60qSva6eVnKmEsGfChUcKtEsTa28lQoOUmEwip1KIme40UIdesRBEpLCvLJej72; path=/; expires=Fri, 21 Sep 2018 21:38:56 GMT; HttpOnly
X-DirectAdmin: timeout
    "error": "Your connection has timed out",
    "extended": "true",
    "result": "Consider disabling the <a href=''>Folder Usage Count</a>."

direct_crons and PATH= SHELL= HOME= fixed

The new direct_crons feature was not correctly handling any manually added PATH, SHELL, HOME variables in a Users crontab, thus generating:

User cron parsing error. Check crontab.conf files


Error Parsing Cron File

depending on which area you're viewing crons from in DA (Admin Level -> All User Crons, or User Level -> Crons)

The fix will respect the setting and will return it back into the file when it's re-saved by DA.


Tested, and it also works with direct_crons=0, which does then save the PATH in the User's crontab.conf, but only if it was in the crontab, then you set direct_crons=0, and save.

Else you'd need to manually add the PATH into the Users crontab.conf, eg:

[root@test directadmin]# cat data/users/cronuser/crontab.conf
0=1 1 1 1 1 /home/cronuser/
1=2 2 2 2 2 /home/cronuser/

but I doubt how many people would be using this method, as with proper support for:


there shouldn't be any reason to have the middle-man file in use.

Cluster: Remote E-Mail Account sync not working fixed

Portions of the E-Mail account sync were working (unsure when bug was introduced), but fixed now.

Accounts can be created and changed.

Deletion did not have the issue.

Dovecot was not reloaded after renewal, so new certs were not seen fixed

Bug where dovecot did not get reloaded if:


was used, as that setting caused the loop to "continue;" before counting that renewal.

Outside of the loop, it then sees 0 renewals, so dovecot didn't get reloaded.

LetsEncrypt: remove prompt = no from openssl request fixed

Only report is affecting Debian 8.

Creating an openssl request generated:

error, no objects specified in config file

problems making Certificate Request

solution was to remove;

prompt = no

from the san_config.

This is only done for LetsEncrypt requests/renewals.

Other DA ssl certificate requests/renewals will still have prompt = no.

Clean forwarders to delete empty forwarder during email removal fixed

Relating to this feature:

Ability to clear forwarder values when deleting emails

where it gives you the option to "Clean Forwarders" when deleting an email account to ensure it does not forward to a non-existent address,


E-Mail accounts:

forwarder: ->

when the E-Mail address is removed, with the "Clean Forwarders" option enabled,

DirectAdmin would have removed forwarder from foo@domain.

Once removed, there would be 0 destination, and DirectAdmin would have set it to :fail:

The premise was "correct" should there not have been a foo@ email account, which was not considered.

Fix is to fully remove the alias if there are 0 recipients, rather than setting it to :fail:

If a forwarder is removed, the result will no longer redirect to the email listing, but instead show a result page/message notifying that the zero-recipient forwarder has been removed.

For cases without the foo@ email, this would still have the same behavior, assuming your catch-all is still set to :fail:.

json encoding double escaping newlines fixed

The DA json encoder was encoding newline characters as:


when it should have been encoded as:


The ability to control which encoding is used is now optional, and can be controlled in the file:






If the setting is absent, it will default to "legacy", double encoding &bsol;&bsol;n

This also applies to line feed:

\\r vs \r

The other escaped values were already correct:




Both the enhanced and evolution skins that are included with this release, pre-release, or downloaded will contain:


But the internal default to legacy is to still support old Evolution skins that rely on the double encoding.



but only if the setting it present in the skin.conf.

If not, it won't be set, and you can assume SKIN_JSONFORMAT=legacy is in use.

Forwarder should not forward to itself fixed

Added a check to prevent a forwarder from being created/saved if it has it's own value added to the list.

Forwarding to itself will cause an unrouteable address error.

Major optimization in table search speed fixed

When using the Admin main page search tool to find a User or Domain/Pointer via the "contains" option, the table does a pre-search on values, before deciding if it should be added to the table.

This applies to only Users or the Domains/Pointers column in this particular case.

Optimization on how the contains search is done to exponentially speed it up.

DNS write race condition fixed

Although we've not had any reports, if there are 2 processes trying to write the DNS file at the same time, there could be a possible race condition.

DirectAdmin does first write to:

before doing the checks on it through named-checkzone, but it's possible while the checks are in progress, another process could in theory write to the temp, causing a possible rename of a half-written file.

We've not seen it, but as a precaution, we've change the temp file to now be;

where 12345 is going to be the current process ID of the running process.

When the "rename" happens, the file system would only allow one at a time, so worst case it would be a theoretical rename failure and a message shown,

but the rename that succeeded would have valid file contents.

Enable login_keys for Admin accounts by default fixed

Because Admin accounts have no packages, any settings that should be on by default, must be set into the POST as it's passed through each class inheritance.

The login_keys variable was not set in this pass-through, thus it was not enabled by default.

Add it to the POST, so that it's always enabled by default (assuming the feature is enabled, which it is, by default)

Backup: create recursive path when needed fixed

When creating a backup, DA ensures the path it's being backed up to exists.

However, with the "Append to Path" feature, you could have specified dated sub-directories which would require recursive creation.

This change swaps out the single directory creation to a recursive one.

nginx_server_redirect.conf to redirec https to https (TEMPLATES) fixed

Domain pointers with nginx had a bug, where an https pointer would have redirect to the http domain, instead of the https domain.

Changed the nginx_server_redirect.conf to redirect to https main domain if it was an https pointer.

Basic locking for LetsEncrypt requests fixed

User LetsEncrypt requests run in the background.

The dataskq can take up to 60 seconds before it picks up the task.queue request to run it.

This allows more than enough time for Users to make many requests, causing issues with multiple dns settings at the same time etc..

Basic locking will now happen for background LetsEncrypt requests.

Any further attempts to do another background request will generate the error:

"Domain is already processing an SSL request. Please allow it to finish before making other changes."

Only the dataskq processing that letsencrypt request will clear the lock.

If your dataskq isn't running, the lock expiry time of 5 minutes applies, so if the dataskq failed to clear it (for whatever reason), future requests will still work after that time.



Rename domain missing .cust_httpd.# fixed

There are other custom tokens that could exist for:


for example (also for cust_nginx.#) for any number, 1, etc..

Instead of tracking each possible filename, DA will now search for any:*

leftover after the known renames, and if found swaps the with

special password characters break backup_roundcube.php and restore_roundcube.php fixed

The new default da_admin passwords, as decided by the, now have special characters.

This can cause issues with php's parse_ini_file function, as the password in the mysql.conf should be "quoted" for this case.

However, the mysql.conf is read by DA and cannot be used.

Simple fix is to check the loaded password after the parse_ini_file call, and if it's not long enough to be a valid password, the backup method is used.

This will read the /var/www/html/roundcube/config/ with an include_once, making it's parsing very easy and reliable, so this fallback could almost be preferred.

Direct crons and User suspension does not restore crons on activation fixed

Bug where the un-suspension of a User does not restore their crons from the crontab.conf file.

FileManager: allow compress file with modified/missing sources fixed

Similar to an issue the DirectAdmin Backup process hit a while back:

Allow tar exit code 1

when compressing a tar.gz file in the FileManager, if a source file changes or goes missing during that creation, tar can throw either code 1 or 256.

As long as the default directadmin.conf setting allow_backup_exit_code_one=1 is set, then these 2 tar exit codes will be allowed and will not throw an error.

Packages: Disabled text input if unlimited already set (SKINS) fixed

For Reseller and User packages, if a package is saved with "unlimited" for a given parameter, when viewing that package, the associated input should be disabled.

Previously, only the inodes entry was set to do this, mainly because it was unlimited by default already, while others are not (for new packages),

but the change now applies to all DirectAdmin package items that can be set to be unlimited.

Toggling the unlimited checkbox activates/disables the input item as usual.




Added tokens:





To the 2 skins, in their respective <input type=text> entries, so if a package item has unlimited set, it will be filled with 'disabled" by default, else blank: "".

Last Updated: