Version 1.56.0

Released: 2019-03-18

Subdomain owner check to include User domains below a Reseller new

Relating to this feature:

Option to prevent creating subdomain of a different user.

If a Reseller is creating a domain, this change will allow them to also create subdomains on any domains owned by any User account they've created.

So say a Reseller creates a User with

The Reseller will then be allowed to create under a different User, or their own account.

However, it does not allow Users themselves to create domains, or change their domain a subdomain value, belonging to a different User.


Widgets (SKINS)(LANG) new

Widgets are tabs that can be added/removed/re-ordered which let the User customize their main screen for the current access Level.

To enable widgets in your skin, you must first create a skin.conf, and add:


then setup the default widget lists in the skin.conf, and defaults if unset, eg:


Just as an example. The WGT_ values are your own creation, and can be named whatever you want. (A-Z and _)

If unset, the lists will be saved into the admin.conf, reseller.conf and user.conf.

The order of the list in the conf file determines the order the widget is displayed.

To specify what each widget will show, you must create a 'widgets" folder in your skin directory,

and each widget must have the files:

  • tab.html
  • content.html
  • lang.txt

DA will set a token called:


which starts at 1 and counts up for each widget used.

All global tokens are available.

But specific table data, use ajax or API type calls for detailed info for your widget.


the lang.txt is just the base translation file.

It might have TITLE and DESCRIPTION, but can also have other tokens you want int the widget content.

The widgets can also be included in the actual language packs, if you wish, eg:


where "en" would be for english, and WGT_ALL_USERS.txt is a duplciate of the lang.txt file, but for this given language.

The "InternalText" class was adapted for this, so the search priority is:

  1. Current skin, current language widget .txt file

  2. Default skin (usually enhanced), then current language widget .txt file.

  3. Widget's lang.txt

Note, we're going to standardize the language packs, so both the enhanced and evolve skins can use the same packs interchangeably.


New json-only output command for widgets:


List all loaded, widgets, and available widgets:



        "WGT_ADDITIONAL_DOMAINS": "Description",
        "WGT_USER_STATS": "Description",
        "WGT_DB": "Description",
        "WGT_EMAIL_POP": "Description"
        "WGT_SYSTEM_INFO": "Description"
        "WGT_RESELLER_STATS": "Description",
        "WGT_LIST_USERS": "Description",
        "WGT_SKINS": "Description",
        "WGT_IP_CONFIG": "Description",
        "WGT_SYSTEM_INFO": "Description"
        "WGT_ADMIN_STATS": "Description",
        "WGT_ALL_USERS": "Description",
        "WGT_SHOW_SERVICES": "Description",
        "WGT_IP_MANAGER": "Description",
        "WGT_SYSTEM_INFO": "Description",
        "WGT_MAIL_QUEUE": "Description",
        "WGT_LICENSE": "Description"
Add a widget to a given level


GET options:



where position lets you specify where it should go,

else it will go to the end.

Position 0 is the first position.

Move a widget up or down


GET options:
direction=non-zero positive or negative integer, eg 1, 2 or -1, -2, etc..

where direction shifts the widget by that amount.

Delete a widget:


GET options:
Reset the widget list


GET options:

resets to the default from the skin.conf.


All of the action calls (add/move/delete) will get a json array either like:

    "success": "widget WGT_EMAIL_POP set to 4"


    "error": "widget is not in the allowed list"

so it will be either a "success" or an "error" with a related message.

SPF: Swap old server IP with new server during restore new

Backup will now store the old "server=" in the backup/user.conf, so DA will know what it was for swapping in the TXT/SPF records.

Only applies if you ensure the spf restore setting is enabled:

"Restore with SPF values from backup. (unchecked: Use local spf values)"

This should be fairly smart, relating to this similar restore change:

Restore: additional IPs for Users and domains

where a list of old to new IPs is assembled.

This change expands on that list, by also adding the old server IP to the new server IP.. so any other areas that had the server IP in the zone should be swapped to the new server IP.

CMD_FILE_MANAGER: action=parent_tree new

For json calls like this:


Returns 1 level of everything the current branch touches, but not lower.

This will be much faster for navigation trees, vs action=tree.

Sample output:

    { "path": "/domains/", "dirs": 1, "files": 12, "files_size": 12949 },
    { "path": "/domains/", "dirs": 0, "files": 1, "files_size": 0 },
    { "path": "/domains/", "dirs": 0, "files": 1, "files_size": 206 },
    { "path": "/domains/", "dirs": 1, "files": 0, "files_size": 0 },
    { "path": "/domains/", "dirs": 0, "files": 29, "files_size": 315070 },
    { "path": "/domains/", "dirs": 0, "files": 5, "files_size": 7979 },
    { "path": "/domains/", "dirs": 6, "files": 1, "files_size": 9703385 },
    { "path": "/domains/", "dirs": 0, "files": 0, "files_size": 0 },
    { "path": "/domains", "dirs": 1, "files": 0, "files_size": 0 },
    { "path": "/.php", "dirs": 0, "files": 5, "files_size": 0 },
    { "path": "/imap", "dirs": 1, "files": 0, "files_size": 0 },
    { "path": "/tmp", "dirs": 0, "files": 1, "files_size": 56 },
    { "path": "/.spamassassin", "dirs": 0, "files": 1, "files_size": 99 },
    { "path": "/Maildir", "dirs": 1, "files": 1, "files_size": 11 },
    { "path": "/.ssh", "dirs": 0, "files": 1, "files_size": 417 },
    { "path": "/", "dirs": 7, "files": 5, "files_size": 509 }

Where it prints lists:

  • all subfoldres of current path

  • all subfolders of each parent path, including /

CMD_FILE_MANAGER?json=yes&action=get_clipboard new

Cleaner way for JSON to fetch clipboard data, and provides more info as well.

Previously, the .clipboard file might not have existed, so was an imperfect way to handle the list.

Sample output:

            "date": "1312712544",
            "gid": "admin",
            "linkpath": "",
            "permission": "744",
            "showsize": "1.90M",
            "size": "1992905",
            "truepath": "/test.tar.gz",
            "type": "file",
            "uid": "admin"
            "date": "1541455507",
            "gid": "admin",
            "islink": "1",
            "linkpath": "./domains/",
            "permission": "777",
            "showsize": "0.03k",
            "size": "29",
            "truepath": "/public_html",
            "type": "dir",
            "uid": "admin"
            "date": "1539610802",
            "gid": "admin",
            "islink": "0",
            "linkpath": "",
            "permission": "755",
            "showsize": "4.00k",
            "size": "4096",
            "truepath": "/test",
            "type": "dir",
            "uid": "admin"

Note: directory size is not recursive, this would be too slow and beyond the intent of this feature.

Evolution: Widgets for plugins new

Ability for plugins to provide their own widgets.

Sample plugin to test with:

but rename it to hello_world.tar.gz before installing it, as DA would end up calling it hello_world-1 and the links inside would be broken.

This 1.1 path is just temporary for testing.


similar structure to skin.conf, eg:


Where the format should start with the given level, as per above (eg: WGT_PLUGINS_ADMIN to match an admin level call)


Can have multiple widgets for each level, separated by a colon.

By default, the plugin.conf: "name=" vaule will be the text for the plugin.

For other names, or if you have more than one widget, you can set that widget name in the plugin.conf, eg:


TODO: language/translation ability for widget texts.


Your plugin widget name mus start with one of:


the suffix after this naming can be anything you want, but best to keep it unified with something similar to your plugin name.

This is to avoid duplicates.


The default request:


will include any plugin widgets/names in the usual area (eg: user_widgets),

but any extra widget tabs will be in an array beside user_widgets, eg:

            "img": "CMD_PLUGINS_ADMIN/hello_world/images/hello_world.svg",
            "tab": "Details from tab.html",
            "title": "Hello World!",
            "url": "CMD_PLUGINS/hello_world/index.html"

where, if a widget is listed there, this should take precedence (As you'd likely not be able to get the info from anywhere else).

Although in reality, if you see it listed there, it's not likely a usual widget, so use it's data either way.

Similar for reseller_widgets_tabs, admin_widgets_tabs.


new command options for CMD_WIDGET, eg:


where we want to "show" the current plugin widget.

The "item" can be one of:


where tab is the left side html to click each widget.

main is the right side that fills the widget content

and both returns both the tab and main content (default if no "item" is passed)

The "level" variable is not required, as DA can decipher it from the WGT name.


Similar to the above show= call, you can alternatively use:


for example, in addition to the rest of the requests.

You can still use show= in addition to select0+.

Note that select0 is the trigger to look for the list, so you cannot start from select1 and go up.. must start from 0.. then any number is fine (does not need to be in order)








which will be run through the plugin system, so you can execute code there as need, but not if it's ever going to be slow.


The tab.html is the basic description inside the tab itself, but should not include the tab title.

See the WIDGET_TEXT, above for that.


The url.html would need to echo something like this:


without a trailing newline

pointing to where you want the "view full details" link to take you, usually a link with the full output, rather than the minimal output from main.html


main.html is the right-side of the widget.. the html data shown when you click that widget's tab.

Can be tables, whatever you'd like.. a staging point, etc.. but as mentioned, it's best that if it's slow, you use ajax to load some other data.

The widget call won't return until everything is ready to go, so if you have a slow call, it will hold up the entire widget page.

Instead, use ajax to make a call to your plugin, perhaps in an iframe or something like that, to load the data in later on.. so the page can be shown more quickly, and then the widget main data just filled in once the ajax call is done.

To avoid JS conflicts with the skin, and to satisfy the quick page loads, it's likely simplest to use an iframe in main.html, and load your other page inside that iframe.


img.html returns a URL the skin can use as an image/icon to be shown in the tab itself. Use .svg if you can, square aspect ratio.

Do not output a trailing newline character.


vue.html, similar to the other one-line URL output script, this will output the URL to access to your /vue/* files.


title.html - the title of the widget. This is a script, like all others, and the LANGUAGE env variable is set (Eg: to 'en' or other)

So if you want your plugin widgets to support different languages, check this variable and output as needed.

Stick with UTF-8 encoding, which is what Evolution uses.

If you do not plan on translating your plugin, then we don't recommend using this file, in order to improve performance.

If absent, DA will grab the "name=" from your plugin.conf instead..

Or if the widget name is set in the plugin.conf, you can override the title that way too

eg in plugin.conf:


widgets are typically loaded with ajax or in real-time when you click that widget tab.

So the main html area usually should not be your final data..

instead it should contain code to write that content to the widget, so as to not slow down the loading of the widget list.

If running the tab or main code is not a time-intensive run, then you could just load your final data directly from the tab/main html files.

But if they could ever be slow, you could have the main/tab code be fully static (more or less), and have the time-intensive call be done,

loaded from a CMD_PLUGIN/yourplugin/user_widget.html, or example, and load that into the main area, using ajax/JS type code from your main.html


you can specify:


in the plugin.conf to have all plugin widget to refresh every 10 seconds, done automatically by the skin.

Will basically just reload the main.html area.

You can restrict it to only given widget with:


to only have WGT_PLUGINS_ADMIN_HELLO_MARS refresh every 10 seconds, but all other widgets in the plugin will not refresh at all.

Span all levels with this setting. Cannot currently specify different interval


When deleting a plugin, the user_widgets value in the user.conf (and other levels) will not be immediately touched.

The full list of possible skin and plugin widgets is known, so if the combined list of possible widgets does not list a given widget that the user.conf is trying to use, it's not shown, and removed from the user.conf's user_widgets list.

always_load_all_script_env_vars for session variables new

The and will end up loading in some specific environmental variables.

In some cases, these still hang around for other custom hook scripts, which is sometimes useful.

The issue is that we might not want to impose the large overhead of these scripts, just to have those variables loaded.

Solution is a new internal variable, off by default:


So that when you turn it on in the directadmin.conf:


even if the all scripts are not present, DA will still load in the environmental variables from these scripts, so the might be available later on.

Default crypt_method=6 for sha-512 new

CentOS 7 and FreeBSD 11 (and all going forward) will have their internal default changed from:


to now be:


any new password being saved by DA will now use this format.

Existing md5 formats will still continue to work normally, as the crypt() function knows the has type when checking.

php-fpm.conf: pm.max_requests = 500 new default (TEMPLATES) new

To clear possible memory leaks from php or 3rd party libraries, reloading the php-fpmXX services after 500 requests can be done.

If there are leaks, this will help keep runaway memory leaks to a reduced amount of overhead.

New default in the template:





pm.max_requests = |MAX_REQUESTS|


where the |CUSTOM1| token can be filled to override this limit, but setting something similar to the |?MAX_REQUESTS=500| in the CUSTOM1 token, but with a desired value.

Using 0 will disable the reload count (which is internal default in php, if unset).

reseller.conf: suspend_at_limit override new

Previously, the only global setting for "Suspend at Limit" for Reseller accounts is at:

Admin Settings -> Suspend a Reseller and their Users when the Reseller goes over their Bandwidth limit.

This feature allows the variable:


which can be ON or OFF, where the set value will override the global Admin Settings option.

If no suspend_at_limit is set in reseller.conf, or it's set but not set to ON nor OFF, then it's ignored, and the global Admin Settings value is used.


The admin.conf uses suspend=YES|NO for the global feature, but the decision was made to match the user.conf suspend_at_limit naming scheme.

Plugins: admin_run_as=root new

Admin Level plugins are now allowed to run as root, but only if:


is enabled in the directadmin.conf,

and if the plugin has this set in it's plugin.conf:


Reseller and User Level plugin call cannot run as root.

If you need root access, you'd need to use a wrapper.


Running a plugin as root can be very dangerous.

If you chose to use this option, only do so with an extreme level of variable input sanitation and checking.

If making calls to things on disk, ensure nothing passed with the input can be used in a mischievous manner (like paths with ../ in them, etc).

If you make many calls which could be run as a less privileges User, consider using this guide to make those call with sudo AS that user, instead of full root:

allowing your script root access, but the security for the user-access calls from that script.

Load Notice: (TEMPLATES) new

Another script at:


which can be cutomized by copying it to:


which will output the top 10 processing using the most memory.

As well as vmstat to get more info on the swap memory usage, 3 iterations (3 second delay, which is fine, as it's called by the dataskq in the background)

This script is trigger when the load of the box is above the amount specified by directadmin, eg:



added token:


CMD_JSON_VALIDATE: type=email allow check for mailing list new

Related command:


can now be extended to:


which also checks to see if a mailing list exists, if you're creating an email account.

Returns json error if hit:

"There is already a mailing list alias with that name"

custom scripts variable: login_key_name new

Similar to the "login_as_master_name" variable that's set if present, a new variable will be set in all custom hook scripts, if it's available:


which will be the name of the key used to login, if the login was done using a "Login Key"

Note, that custom hooks scripts can be called by things that do not use a login at all, such as restores, or things like that.

So there is no guarantee that it will be set if any of those other methods are used... not to mention all normal logins not using a login key 😃

Plugins: ability to update with task.queue new

You can now tell DA to update plugins from the task.queue. Sample command:

echo "action=plugin&value=update&select0=hello_world" >> /usr/local/directadmin/data/task.queue

You have multiple Admin accounts, you can specify the one you want by including username=admin to the list.

If no username is passed, directadmin will read the admins.list file and use the first entry found.

Some plugins do need the correct Admin account to be set, so ensure you're calling the update as the correct account for those cases.

Plugins: global vue new

If you set:






in your plugin.conf, the plugins request page:


will add (use admin as an example)

"vue": "/vue/admin.js"

into the "hooks" array, for that given plugin, if the file exists:


Also applies to reseller.js and user.js.

Evolution will pick this up for it's vue system.

If vue= starts with http, then a vue override mode kicks in.

In the above example, we show:


which starts with http, thus applies. In that case, if the admin.js (or reseller.js/user.js) exists, the vue json output would be:

"vue": "http://localhost:3000/admin.js"

SSL Certificates: Allow in Common Name new

New directadmin.conf option, with internal default:


where, if you set it to 1 in your directadmin.conf:


this will use:

CN                      =

instead of the previous value using 0:

CN                      =

All other settings will remain unaffected.

1.56.3: Changed the CMD_SSL page to use |WWW_DOMAIN| token for "Common Name".

With certificate_common_name_with_www=1,
With certificate_common_name_with_www=0,

so that when DA prefixes the common name with www, it's not doubling up in the san request.

MySQL: new user methods: ALTER USER, DROP USER, etc. (SCRIPTS) new

TODO: Known bug: MySQL 8.0 does not allow "IDENTIFIED BY" with GRANT, so that also needs to be changed.

Relating to the forum request:

We've added an internal option:


where if you set it to 1 in the directadmin.conf and restart directadmin:


DirectAdmin will use the newer methods for controlling accounts, such as:


Users are still added to DBs as before, using the GRANT option.

Listings of accounts will continue to use the SELECT method, local retrieval shouldn't affect saving to a cluster.




removed the old UPDATE mysql.user and UPDATE mysql.db methods, in favor of the RENAME USER for the system account name.

Had to loop through each access host for it to apply to all entries, but it simplies this in that is covers both mysql.user and mysql.db in one call.


There was a massive number of query lines touched, and new function calls created to handle this alteration (the "diff" output has 20 pages of line changes)

The main issue was that the previuos UPDATE calls handled all "host" entries by simply omitting them.

The ALTER/DROP/RENAME, etc.. all require the specific host to be used (cannot specify %, as that's also a literal value), so functions with loops to query all hosts, and update every entry one by one were needed.

As such, during the BETA release phase, this would be a key point for testing, backup/restore, as well as db/user creation, password changing, access host adding/removing, and privilege changing.

Also, the new GRANT/REVOKE method (also requires calling once per host), but if you set all privileges to "No" ('N'), the REVOKE call will actually fully delete the line from the mysql.db, which is not what we want.

So DA now blocks anytime you try to set N for every privilege, only for the mysql_user_new_user_methods=1 (0 still allows it)

Domains: user.conf to block domain adding/deleting new

New custom user.conf option:


where it's not present in the user.conf by default, and internally loads to 0.

If added to the user.conf, you can set it to 1 or 2 where:


will block the ability to delete any domain.


will block the ability to add or delete any domain.


The directadmin.conf file now also has a duplicate of this setting, which will be the default, taken first.

It defaults to 0 internally.

If set to 1 or 2 or in the directadmin.conf, this will be the new global default.

The user.conf value will override it, if set (eg: can override back to 0 in the directadmin.conf, for example)


Up to 4 php versions in DirectAdmin and Custombuild new


With this change, the CustomBuild 2.0 options.conf can support up to 4 php versions, eg:


No templates changes are needed.

The POST to CMD_DOMAIN action=php_selector now supports values upto the number of php versions installed.

So php1_select can be set to 1, 2, or 4.

The php2_select can also be set to 0, but the current internal default doesn't show the 2nd php selector.. since most people don't need 2 php versions live at the same time.

Any of the 4 versions can be set to php1_select, which is sufficient almost all the time.

Be sure to run "./build update" to get the latest version of custombuild.

Also updated CMD_CUSTOM_HTTPD to show correct php-fpm select (also defaults to just 1 php now, based on php_version_selector=1)

And fixed a bug where "php 7" should have shown up as "php 7.0" in various areas.

Drop TLSv1.0 and TLSv1.1 in DirectAdmin new

DirectAdmin will now fully drop older TLS methods from connecting to port 2222.

For older binaries that don't support TLS 1.2, it will revert to the highest possible server method, with priority top-down being:




where SSLv23_server_method does not imply SSLv2 or SSLv3, but is there from the older openssl libraries to be used for all possible methods (name is misleading)

There are not currently any directadmin.conf options to downgrade your server_method, but if you do need it, let us know and we can look at adding them.

You really should be upgrading your client if it doesn't support TLS 1.2 😃


If you really need to connect to a DA box, but the client does not support TLS 1.2, then the current workaround is to set that DA box to run on both https:2222 and http:2223 at the same time,

where 2222 is still https/secure, but 2223 is not. (swap 2223 with some high random port number, and set your firewall to only allow the client IP to connect to it)

The DA settings for https on 2222, and http on 2223 would be:


where 2223 uses SSL=0, but the secondary setting ssl_port=2222 runs a 2nd fork of the master for SSL/TLS 1.2 connections, as before.

If the client is another (older) DA box, then the connection should be using a Login Key, restricting both the IP, functions, as well as the firewall.

It's not perfectly secure, but neither is using TLS 1.0/1.1.

php-fpm.conf to support ACTIVE_USR_LOCAL_PHP_LIB token new

new token, filled based on the options.conf settings for php, only filled with active php versions on the system.


sample output:


if you have this in the options.conf:


regardless of which php modes the User has enabled.

Evolution only at the moment.

The Admin Level -> Admin Backup/Transfer -> Backup/Restore Settings

will now include the related directadmin.conf options in the json out from:


"settings" : ...
            "type": "i01",
            "value": "1"
            "type": "i01",
            "value": "1"
            "type": "ir:-20:19",
            "value": "19"

Where you can add the settings below to the existing action=setting form.

Use only the desired settings, as some are more advanced.. might not be 100% suitable for novices to change them.

For example if you set strict_backup_permissions=0 (not recommended), then you must have backup_hard_link_check=1.

DA will let you set what you want, but the skin should not allow those combinations.

The manual directadmin.conf would allow it, but would be somewhat of a security issue, as strict_backup_permissions=0 runs backups as root.

For the novice User, these might be simplest to show:

backup_hard_link_check (only show this option if strict_backup_permissions=1)

But the following settings are available if you want them:


where each setting includes the current value from the directadmin.conf, but also that setting "type".

The current types are:

i01:  integer, either 0 or 1.
b01: boolean, either 0 or 1
i: integer: anything from -2147483647 to 2147483647.
ir:low:high: integer range, with numerical low and high values. See "backup_nice" example, above.

The implementation of the "type" option is a change to the core Config class, where that class doesn't more checking of it's own.

Will be adding those type checks more in the future for other directadmin.conf options, but for now, it's just these backup options, only in the Admin backup/restore settings.

Ability to delete domain in the background via the task.queue new

Related to backgroun User deletion (see related links), this new feature lets you delete a domain from the task.queue.

Sample call:


Where options:

requestedby=admin  : controls where the result Message is sent to in the Message System. Success or fail.

It does not have to be the owner of the domain, but can be.

The actual domain owner is looked up for each domain. :  the domain to be deleted:
( : optional other domains, for any User, does not need to be under the same User.  select2=, select3=, etc.)

Option to notify all Admins about the creation of any account type new

New directadmin.conf option, defaults to:


where, if you set it to 1 in the directadmin.conf and restart DA:


Any account, created by any account type (including Resellers), will notify all Admin accounts through the Message system about the creation.

Sample message:

Subject: User account testtest has just been created

Account resellerbob has just created User testtest from IP


Ability for Reseller/Admins to message all E-Mail accounts on the system (SKINS)(LANG) new

Tool giving Admins/Reseller the ability to quickly message all email accounts on the system.

Must be "local" accounts, meaning it will only deliver to local domains listed in /etc/virtual/domains.

Relating to the "Create Message" tool, eg:

A) Admin/Reseller Level -> Manage Tickets -> Send Message

B) Admin Level -> Show All Users -> (select user checkboxes) -> Send a Message

C) Reseller Level -> List Users -> (select user checkboxes) -> Send a Message

This tool will show a 2nd checkbox beside the "Email Only" checkbox, when the "Email Only" checkbox is checked.

The new checkbox shows up as;

[ x ] All E-Mail Accounts

and if this is checked when submitting the "E-Mail Only" message, that message is:

  1. Delivered to all User/Reseller accounts that were specified/selected, just as before

  2. Plus all E-Mail accounts that exist under domains for those selected User accounts.

For Admin's, as there is no "All users on the system" option for A/B (above), the only way to truly message all email accounts on the system,

is to use:

Admin Level -> Show All Users -> Advanced Search -> Items per Page = "All" -> Search

Then top right, click "Select", scroll down, and click "Send a Message".

Ensure both "E-Mail Only" and " All E-Mail Accounts" are both selected when sending the message.


Since the list of email accounts is assembled before delivery, this list can get to be rather large.

As a result, like the user=multiple, user=all, user=all_resellers options, the all_email_accounts=yes option will also push delivery to the background with the dataskq,

and the sender will get a Message System notice after it's sent.


The "All E-Mail Accounts" will send those virtual E-Mails as the Admin/Reseller, rather than "diradmin" which is used for the system account "E-Mail Only" part.

This means that if there are any blocks by exim for this account to send emails, they will be handled accordingly.

Note: the SpamBlocker outbound limits do not apply to local email delivery, so that's not really an issue.. probably would mainly just apply to full system account email delivery blocking, like:






within the |*if SHOW_EML_ONLY="yes"| section add checkbox for <input type=checkbox name=all_email_accounts value='yes'>

This should be hidden/disabled if the "E-Mail Only" option is not enabled, and visible when "E-Mail Only" is enabled.

Note that the E-Mail Only checkbox can be checked when it's loaded, so if it is, show the new checkbox on page load.



LANG_ALL_EMAIL_ACCOUNTS_DESC=Selecting this option will apply only to E-Mail accounts under to the selected DirectAdmin accounts at or below your level


627=%s account %s has just been created
630=Account %s has just created %s %s from IP %s

MX record templates (SKINS)(TEMPLATES) new

Users can pick google/zoho from a list, to remove their MX records and replace them with the values in the template.

New item at the bottom of the page:

"Set remote MX records"

Enabled by default in the directadmin.conf:


to disable it, set:


and restart directadmin.

Note: It does not change the "Local Mailserver" checkbox, this is still manually controlled.


new directory:


with 2 default templates:


Containing their default MX record settings for those services.

The top will also set:

|?NAME=Google Suite MX|

To give the select box a human readable name, but the selected value in the form is still


You can add more .txt files into the templates/mx directory (in theory they could be overwritten if we ever "happen" to include files with those names),

but if you want to override the 2 .txt files.. or just add more in a more clear way, this is the custom mx templates path:




uses the token check, representing the mx_tokens=1 directadmin.conf value:

|*if MX_TEMPLATES="1"|

The |MX_TEMPLATES_SELECT| token contains the selectbox for the available templates, plus "default" to revert the User back.



method: POST


will return one new entry if mx_templates=1 is enabled in the directadmin.conf.

If it's not present, then it's either shut off, or an older directadmin version.

            "selected": "yes",
            "text": "-- Select MX Template --",
            "value": "..."
            "text": "Revert to Local Default",
            "value": "default"
            "text": "Google Suite MX",
            "value": ""
            "text": "Zoho Mail",
            "value": ""

Rspamd: binary replacement of SpamAssassin (TEMPLATES) new

Early support for Rspamd, which is a compiled binary, so should have much better performance than SpamAssassin.

It's a smtp-time process only, so you must be using EasySpamFighter to use rspamd.

All existing SpamAssassin settings will remain, and be used as the config for the per-User rspamd settings.

Specifically, the /home/username/.spamassassin/user_prefs file.


new template:



When you save the SpamAssassin settings through DirectAdmin, the the per-User config is save at:


which is loaded from the included file, if it exists:


which then loads in the this file, which holds the list of all User configs:


where the list points to the users.d/username.conf.


If you need to add other custom settings, the file you'd put that in would be:


just be sure NOT to remove the directadmin-users.conf include.


CentOS 7 (known to work, so far)

cd /usr/local/directadmin/custombuild
./build set spamd rspamd
./build rspamd

Note that there is no directadmin.conf settings.

DA will always grab the spamd value from the options.conf to know if it's on or off, to decide how to behave.


Script that replace the spamassassin_*.sh counterparts, in the directory:


Packages: copy/paste import/export of all packages (SKINS) new

New button on both packages pages:

Admin Level -> Manage Reseller Packages

Reseller Level -> Manage User Packages

Showin "Export Selected"

When you select the desired packages, and click this export button, it will generate an encoded output of those package details.

You can then copy the data, and paste it into the same page on some other server.

The "Import" section is just below, click the + to show the "Import Packages" form.. where you paste in that data.

This action will both create, and overwrite existing packages if they exist.

Similar to any other package changes, any User/Reseller controlled by this account which is set to this package, will have their settings updated accordingly.

If you have many Users, please wait as it can take several seconds depending on the number of Users, and disk I/O speed.


Exporting data:


method: POST

export=Any text



basically uses the same form as the "delete".. so you can still use the delete0.. both select0 and delete0 are accepted, they fill the same list.

Added support for select0 for import to avoid confusion in assembling the form.

JSON: the export data is set in the standard dynamic "details" without any surrounding textarea, etc.

Importing data:


method: POST
import=base64encoded data



added new include:


before the footer.

files_reseller.conf, new entry:


and new file:


containing the form.


new items:

.text_align_left {

text-align: left;


.w64chars {

width: 550px;


.h34rows {

height: 550px;


pre script for overusage: new

Related to

post script for overusage:

new optional custom script:


where a non-zero return code causes the delivery of the overusage notice to the User not to be sent to the User, nor to the creator.

It does not affect anything else in the tally of that User.

Assign Roundcube/Webmail data to "email accounts" instead of "email data" new

New directadmin.conf option, defaults to:


which maintains all previous defaults where webmail data (squirrelmail/roundcube) are part of the E-Mail Data backup checkbox.

If you set it to:


this re-classifies the webmail data over to "E-Mail Accounts", so you can exclude the "E-Mail Data" checkbox in the backup, but still have your webmail data backed up.

This could be useful for cases where you want your RoundCube database backed up and restored, but want to exclude email Maildir data, as Maildir can be easily transferred with rsync.

Allow usernames up to 30 characters new

Previous hard default was 14 character max.

As newer version of MySQL/MariaDB support longer names (or could be modified to do so), DirectAdmin has been changed to allow a longer username length.

Unless you really need it, we'd recommend sticking to 14, eg:


but you can set it to:


if needed. Be sure to confirm with the MySQL/MariaDB versoins you're running how long they support, else they'll throw errors when DA tries to set a long value.

Not 100% certain on the required version for longer values, but:

  • 16 characters before MySQL 5.7.8 .Aug 25, 2016

  • up to 80 characters for MariaDB 10.x

So to increase the max_username_length=30, you'd need at a minimum:

MySQL 5.7.8+


MariaDB 10.x+

But regardless, before setting this, you may want to confirm for yourself.

Take the da_admin login from:


login to your server at:

and click the "mysql" database on the left.

Click the "db" table on the left, below the mysql db.

Near the top, click the "structure" tab.

Under the "Type" column, see the char() length for both Db and User.

The User seems to be the smaller of the two, so if it shows 32, then you can use max_username_length=30

If the User type is char(16), then the max_usernamae_length=14 would be as high as you should set it.

Ensure User's are feature-reduced if Reseller is reduced fixed

When modifying the reseller.conf data of a Reseller, if a setting is shut off, say "SSH Access for Users",

ensure that all packages are updated accordingly to shut it off, and go through all User's below that Reseller to ensure they can no longer use, for example ssh.

This will only apply to the on/off checkbox items, and not the numerical/unlimited items.

For the given options, when disabled, for each User below this Reseller, if that User's user.conf's value for this setting differs (eg: it was on), that User will have it's data activated (set to the value specific > OFF)

This will also disable the settings for all packages under the Reseller.

However, as this could be a very system load intensive (many read/writes if you have many Users), this operation will be done in the background.

In this particular background disabling of only the changed setting will set.

So if the set-to-disabled items are no related to some other feature, that other feature will not force-set the User.

For example, if your sshd_config file had the User enabled.. but there was some sync issue where the user.conf ssh=OFF was set, the User would not be removed from the sshd_config.

This is the only case where this assumes things are in sync, and is done solely for efficiency, since activating data on thousands of Users could be very very slow.

All other areas that change User settings, like manually setting a value for the User, or changing a package entry (which updates all Users set to it), will force all settings back in place.


When changing a Reseller's settings manually per-setting, or simply changing that Reseller package,

for any setting that has changed to the OFF value, it will be added to the list.

So if you only disable SSH for the Reseller's Users, and say.. anonymous FTP, the task.queue call will look like this:


(note: userssh converts to 'ssh' when checking user.conf files)

The dataskq will pick up this request, and for each User in fred's users.list file,

if the user.conf has 'aftp' or 'ssh' enabled, they'll be shut off.

ProxyErrorOverride on for nginx+apache proxy VirtualHosts (TEMPLATES) fixed


This setting has been commented out. Done by adding # in front of the entry, eg:

#ProxyErrorOverride on

in the 4 virtual_host2*.conf templates.

Related thread:


Relates to not passing errors to nginx, and instead let php/apache generate the error pages.

The 4 virtual_host2*.conf files will have:

ProxyErrorOverride on

just before the AddHandler for both php1-fpm and php2-fpm.

CMD_ADMIN_STATS not showing all disk usage fixed

Some languages use commas.

Number verification was added to 1.55.0 causing some language formatting of those numbers to fail, eg:


Allow spaces in ssh key comments fixed

DA previously required that the ~/.ssh/authorized_keys file required comments to be a single word.

Code has been changed around to use the key-type (ssh-rsa) to be the anchor point, so it can have the optional settings to the right, and any number of comment words to the right of the key data.

CMD_FILE_MANAGER: ensure $LANG set before calling zip/unzip/tar fixed

When the "extract" button is clicked in the File Manager (eg: on a zip file), the $LANG value set in the environment does affect how the data is output.

For security reasons, and call to any embedded script, DA first clears the environment to ensure nothing malicious was set.

However, the call to zip/unzip (for example) does use the $LANG env variable to determine the encoding of the filenames shown, when displaying files.

Also, it only applies to the CMD_FILE_MANAGER/CMD_API_FILE_MANAGEr, due to the skin file pre-processing, in advance of the chroot (which doesn't actually happen in this case, but it's pre-processed solely on the filename)

As a result, even if the LANG was set correctly, if your skin makes an embedded script call in CMD_FILE_MANAGER, then result will be an empty environment, so the call later on to unzip would mean LANG is not set..

If you needed special encoding, the filename would end up showing in the ugly unicode format, eg:


Note, the extraction would still work correctly, assuming you use the directadmin.conf option:

extra_unzip_option=-O cp396


To resolve this, prior to the zip/unzip/tar calls in CMD_FILE_MANAGER,

DA checks for the existence of the LANG env variable.

If it's unset, DA will set it to en_US.UTF-8.

It's more desirable to use the skin's encoding name, but as it's either iso-8859-1 or UTF-8 for example, these are not valid LANG variable names.

Since the number of affected people are quite low, this should be fine.

Delete vacation message along with email fixed

Deleting an email account did not previously remove it's associated vacation messages, if present.

Now it does.

Reset DNS Zone: find DKIM keys from suspended domain fixed

Reset zone now notices the domain is suspended and will grab those keys from the renamed domain.com_off directory.

Restore: additional IPs for Users and domains fixed

When using the "Use the IP stored in the backup" option, DA should try and use those IPs, or ensure it's swapped to a new value if the old value was on the old box for "additional IPs" instead of the main user.conf ip.

Assumes they're already added to the given Reseller account, and set to the correct free or shared state, as desired, before issuing the restore.

Previously, it only looked at the user.conf ip setting for the swap to the new value.

It's now much smarter, and will restore a domain to it's previous value, even if the domain was set to some other IP address.

Aside from not being able to restore a server into a previous state, another issue was if you had this setup:

restoring to a brand new IP:

when restoring this backup, because the IP being search for to swap "from" was only looking for, then dns ended up in a bit of a mess where IPs were not being swapped.

This fix looks for the correct for the "old" IP, to swap to


If using the "Use the IP stored in the backup", attempt to assign it to that User. Important that you have it assigned to the creator first, and set correctly to "free" or "shared", before restoring the User.

If using the "Use the IP from the list:", DA will create the User and domain with the new value, but search for the backup old domain.conf file for the old search value for swapping to the new IP.



LetSencrypt hostname: check for cacert path in case it's not the default fixed

The LetsEncrypt feature can setup a certificate for your hostname.

In this case, as long as everything is in the default place (eg: directadmin.conf cacert=/usr/local/directadmin/conf/cacert.pem),

things should work normally, and the itself will auto-renew the hostname cert when needed and copy it to all different service cert locations:




etc.. for all cert, key and ca root certs.

However, if you've changed the cacert value in the directadmin.conf so that DA uses the apache cert, or some other path, eg:


the script assumes you're dong something custom, and will not copy things around, so doesn't work as usual and the certs will not auto-renew everywhere.

As the is usually not the desired behavior, but might be, although DA won't ignore the setting and change it back to the default (because what's then the point of a setting),

instead DA will do it's best to let you know that this isn't normal, and will add entires to the system.log erortaskq.log and debug output with:

LetsEncrypt: Ssl::get_cert_creation_time: **** Hostname certificate %s is not in the usual %s path. This can affect the's ability to auto-renew. Please change your cacert,cakey,carootcert setting in the directadmin.conf

where %s #1 is the cacert= value from the directadmin.conf

and %s #2 is the default value DA would like to see for things to work correctly.

Note, although DA only checks the cacert value, if you do change things back ensure you do it for all 3 settings, eg:



carootcert=/usr/local/directadmin/conf/carootcert.pem (assuming you use this)

BFM: Roundcube: X-Real-IP (TEMPLATES) fixed

Roundcube apparently now logs information like:


so the brute_fitler.list needed to be updated, and now looks like this:

roundcube1=ip_after= from &ip_until=(X-Real-IP:&text=IMAP Error: Login failed for&user_after=IMAP Error: Login failed for &user_until=%20from%20

roundcube2=ip_after= from &ip_until=(X-Forwarded-For:&text=IMAP Error: Login failed for&user_after=IMAP Error: Login failed for &user_until=%20from%20

roundcube3=ip_after= from &ip_until=. AUTHENTICATE PLAIN&text=IMAP Error: Login failed for&user_after=IMAP Error: Login failed for &user_until=%20from%20

where 2 and 3 used to be 1 and 2, and roundcube1 is now present.

SSL Certificates: san_config: ensure common name is first in the subjectAltName list fixed

It appears as though, regardless of what the CN is set to in the, the first subjectAltName DNS value set in the san_config shows up as the main "Issued To" value.

DirectAdmin will now decide what the common name is (either, or * and will ensure that it's written first in the subjectAltName list at the bottom of the san_config.

Plugins: too many headers block latest available version from showing up fixed

You might see "Cannot find the end of the headers" in debug mode or errors when trying to see the latest plugin version.

Was a limited buffer size blocking all headers from loading in at once.

Crontab to strip double-quotes for no email: MAILTO fixed

If you set a blank email value with the intent not to send any email, it's saved into crontab as:


however, DA was not stripping the "" values when showing what was set, so it caused confusion in the input field with


DA now strips it so it won't show the double-quotes.

If a valid email is set into crontab like this:


DA will also strip that out for the input field to use value='', as it would have similarly shown:


which couldn't be saved like that.

Better validation of IPs (SECURITY) fixed

Better validation of IPs. Intentionally left ambiguous.

Risk level low, as attacker must already be logged into a privileged account, and cannot steal or acquire anything useful.


X-Forwarded-For to allow multiple IPs fixed

Relating to feature:

Allow header X-Forwarded-For header for proxy or load balancers

it allows the client IP to be set via the X-Forwarded-For header.

However, multiple values should be allowed, but DA was only checking for a single valid value, this throwing this error:

X-Forwarded-from IP found ( But X-Forwarded-For value is not a valid IP:,

where is the client IP, is the incoming proxy IP

and is the unexpected "middle" proxy IP, likely between the final proxy and the client.

The fix will be for DA to strip out the extra IPs, since we're less concerned with the middle proxies used to get here.

Restore Domain Pointers to type Alias fixed

Related to this change, which introduced the bug:

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

The new format:

was not being correctly parsed during a restore, causing all new pointers to end up being "redirect" type instead of "alias" type.

Block global username fixed

Added "global" to the reserved username list.

MariaDB restore: use abort_source_on_error fixed

New internal default directadmin.conf option:


if set to 1, and if MariaDB is used, the mysql binary command option:


to ensure that failed restores actually return a non-zero return code.

There are some cases where a MariaDB restores are failing but still returning a success 0 code, which is not correct.


you can confirm if this is being called in the dataskq, level 1000 or higher.

The restore output will include this line:

Database::restoreDatabases: adding --abort-source-on-error to mysql call

Reseller overselling not restoring fixed

If the reseller.conf had an overselling option set, it will now be restored.

(SECURITY) fixed

There was a report of an exploit, and a CVE was created (not by us)

We tested the issue, but were unable to reproduce it, and reported back to Secunia with our results.

During testing, other factors were considered and a different issue was found/fixed.

So the original report appears to be false, but a new/unrelated/unpublished issue was discovered.

Once a short period of testing on this fix has had time to confirm it doesn't break other things, we'll release 1.56.0.



Bartosz Kwitniewski

Last Updated: