Version 1.51.0

Released: 2017-02-09

Backup/Restore mysql charsets new

Backup and restore of databases should maintain same character-sets.

Related query for backup:


mysqldump -uUSERNAME -pPASSWORD --default-character-set=utf8 USER_DATABASE > backup.sql

Related restore:

mysql -uUSERNAME -pPASSWORD --default-character-set=utf8 USER_DATABASE < backup.sql

New directadmin.conf option:


internal default is null (unset), but you can add it to your directadmin.conf, to add extra options, eg:


LetsEncrypt: disable auto-renew (SKINS)(LANG) new

Added a button to allow Users to prevent the automatic Lets-Encrypt renewal.

This does not affect the current cert/key pair, it simply disables the renewal.

Also, deleting the domain will clear the same files as well.

The button will delete these files if they exist:

  • /usr/local/directadmin/data/users/username/domains/
  • /usr/local/directadmin/data/users/username/domains/
  • /usr/local/directadmin/data/users/username/domains/


The API can also make the call:

method: POST
disable_letsencrypt_autorenew=any text



<div style='padding-bottom: 20px'><input type=submit name='disable_letsencrypt_autorenew' value="|LANG_LE_DISABLE_AUTORENEW|"></div>





40=Error Deleting %s


575=LetsEncrypt files have been cleared
576=Error clearing LetsEncrypt files

skin.conf (SKINS) new

New feature for skins, with a file called:


which can exist in the skin's folder in order to affect how DA creates some tokens internally, eg:




Causes the domain select that appears on all User Level pages to not have any javascript, and to have the value:


allowing actions to be controlled by external javascript, like ajax.


Causes the internal Table class to generate <div> output, instead of <tables>.

commands_force_deny to override allowed/deny commands new

Relating to both features:

commands.allow/commands.deny files:

commands.allow and commands.deny for per-user control

and login key allow/deny files:

For both cases, the allow will always override the deny, as it is for various other rues (eg: /etc/hosts.allow vs /etc/hosts.deny), so that logic will not change.

However there are some cases where that's very inconvenient, like where you want to allow a User to run all commands, except denied a few.

A command example would be to:

allow: ALL_USER

in the above example, previously Login Keys would be allowed because CMD_LOGIN_KEYS is a subset of ALL_USER, and allow overrides deny.

New feature to override the allow, and force a deny command.

Internal default:


where any commands listed in the commands_force_deny will override the command being in the allow.

The variable is a list of commands, seaparated by a colon.

The feature really only has any effect when commands are listed in both the allow and the deny.

It's basically used as a tie-breaker, to force deny, when a tie would previously allow the command.

CustomBuild to manage new

Update: Aug 3rd, 2016:

We've added the script into CustomBuild, but it will simply download it and copy it over to:


it will not live in the custombuild/configure/letsnecrypt/ location.

As it seems that the LetsEncrypt project continues to receive many updates/changes as time goes on,

rather than requiring a whole new DA update for each change to the script, we've moved it to CustomBuild.

For backwards compatibility, the custom folders will override the defaults.

The checking order for which script to use is as follows, higher has priority:

  • /usr/local/directadmin/custombuild/custom/letsencrypt/
  • /usr/local/directadmin/scripts/custom/
  • /usr/local/directadmin/custombuild/configure/letsencrypt/
  • /usr/local/directadmin/scripts/

So if you have a custom script from the older directadmin/scripts/custom/ location, it will still trump custombuild/configure/letsencrypt/


New search tool to find data quickly.



GET options:

search=your query

Optional values:


If set, the search_default will initialize all variables to 1 or 0. Else all search_* variables are initialized to 1.

If user_level is passed, this applies the settings on email, ftp, and db. Doesn't touch Admin/Reseller items (users/domains)

Any search_* value will set to that value, after all above conditions are finished being set.

If only a basic "search" is done, without any variables:

  • Admin/Resellers: every thing is searched

  • Users: User Level items are searched.

For any array type, if there are no items, the array won't be passed at all, so make sure to ensure the array exists before starting to read it.


new variable:


uses a floating point number to determine how long the search is allowed to run.

It doesn't use an interrupt, but rather just does a check in between items, and inside loops.

So for example, if the email search takes 3 seconds, ftp and databases won't be searched at all.

Sample output:

    "CMD_SHOW_USER?user=testresell", "testresell",
    "CMD_SHOW_USER?user=testshared", "testshared",
    "CMD_SHOW_USER?user=testuser", "testuser"
    "CMD_SHOW_RESELLER?user=testresell": "testresell",
    "CMD_SHOW_RESELLER?user=testshared": "testshared"
    "CMD_SHOW_USER?user=shareduser": "User shareduser :",
    "CMD_SHOW_USER?user=templtes": "User templtes :",
    "CMD_SHOW_USER?user=testresell": "User testresell :",
    "CMD_SHOW_USER?user=testshared": "User testshared :",
    "CMD_SHOW_USER?user=userofres": "User userofres :"
    "CMD_DB?name=user_test": "Database user_test"

php_fpm_restarts to override reload/graceful new

Some reports of issues with the default actions for php-fpm.

systemd: reload

init.d: graceful

If you're having issues with php-fpm not executing the above command properly for your system, you can add this value:


to your directadmin.conf (internal default is 0), so that it calls a full "restart" for the php-fpmXX service(s).

CMD_FILE_MANAGER action=json_dirs action=json_files action=json_all new

json_dirs: generate a json output for the list of directories in the current path

json_files: generate a json output for the list of files in the current path

Note, if "linkpath" is not empty, eg: "", then the file or folder is a link.


path=/  or path=/some/path







default for this function is 1.

(CMD_API_FILEMANAGER default is 0, but can use api_flags=1 in the GET for more info in the API result)

The api_flags variable is a bitmask for the following options:

#define FM_F_RENAME 2
#define FM_F_COPY 4
#define FM_F_RESET_OWNER 16
#define FM_F_EDITABLE 128
#define FM_F_EXTRACTABLE 256

so to figure out which action a folder can have, check the the bitwise & for the given item.

Example, to determine if a folder is protectable, check:

if (api_flags & 1)
  console.log("folder is protectable");

The json_files can use the ipp=10&page=4 method to limit the output, just like tables.

All table search/sorting/filtering is available, same as Advanced Search for tables.

But the "sub_directories" and "files" variables are not search/sort-able, as they're appended to the data afterwards.

Page/row info is added to the "info" data item at the end of the list.

There shouldn't be any conflicts with /filenames as all files/paths start with a forward slash, and "info" does not.


The json_dirs/json_files output is actually all run by the existing table class.

So anything you can do with a table, you should be able to do with this output.

The difference is the column names and column order is not the same as the Enhanced "Advanced Search" so the column numbers won't be the same.

Note that the columns of json_dirs and json_files ARE NOT THE SAME, so please see the samples for the column number you want.

If we add colums in the future, I'll make sure they're appended to the end, so the numbers for existing columns remains the same.

Use the sample output below for json_dirs / json_files column numbers, below.

api_flags is column 1, and count up from there.

Eg: for json_dirs, to search for a .txt file, you'd want column 6 (path), eg:


You can specify single rules on multiple columns at the same time, if you want to execute a boolean "AND" comparison.

For example:


will show all files that have .txt in their name, and who's size is 1MB or larger.

So when searching, the comparisonX values can be:

  • equals
  • atleast
  • atmost
  • contains
  • startswith

with the given valueX set to what you're looking for.

Make sure the atleast/atmost are only searching on number type columns, else you'd be doing it on strings, eg: a<=b which isn't as useful.

You can then sort, and sub-sort at desired, eg:


where sort1dir is either 1 (ascending) or -1 (descending)

And sort1 is the column to sort.

You can then do a sub-sort if you want, eg:


which is the sorting done when a row in sort1 matches. (usually not required)


Json "info" output sample for json_dirs:

    "api_flags": "1",
    "date": "2",
    "gid": "3",
    "linkpath": "4",
    "name": "5",
    "permission": "6",
    "showsize": "7",
    "size": "8",
    "truepath": "9",
    "uid": "10"
  "current_page": "1",
  "ipp": "50",
  "rows": "10",
  "total_pages": "1"

columns: is a sub-set showing which column each item is at, useful for figureout out which comparison row to work on. (json_dirs != json_files column #s)

Items per page:

If you want to control how many items are returns, set a value:


but usually isn't required.

DA has an internal default of 50.

rows shows the total number of items in the table.

You can pass:


to see page 2, and then you'd see current_page=2

total_pages shows the number of pages available for the given ipp values (internal default, skin.conf value, or get ipp override)

An override recomputes the total number of pages, so would affect the current page, etc..


Sample output for / path:

    "api_flags": "6",
    "date": "1474348180",
    "gid": "admin",
    "islink": "0",
    "linkpath": "",
    "name": "محمود"
    "permission": "755",
    "showsize": "4.06k",
    "size": "4155",
    "truepath": "/محمود",
    "uid": "admin",
    "sub_directories": "1",
    "files": "1"
    "api_flags": "54",
    "date": "1476144611",
    "gid": "apache",
    "islink": "0",
    "linkpath": "",
    "name": ".php"
    "permission": "770",
    "showsize": "4.00k",
    "size": "4096",
    "truepath": "/.php",
    "uid": "admin",
    "sub_directories": "0",
    "files": "10"
    "api_flags": "6",
    "date": "1475065659",
    "gid": "mail",
    "islink": "0",
    "linkpath": "",
    "name": "Maildir"
    "permission": "770",
    "showsize": "1.34M",
    "size": "1409098",
    "truepath": "/Maildir",
    "uid": "admin",
    "sub_directories": "6",
    "files": "5"
    "api_flags": "6",
    "date": "1476075968",
    "gid": "admin",
    "islink": "0",
    "linkpath": "",
    "name": "admin_backups"
    "permission": "711",
    "showsize": "711.5M",
    "size": "746046021",
    "truepath": "/admin_backups",
    "uid": "admin",
    "sub_directories": "20",
    "files": "15"
    "api_flags": "6",
    "date": "1475246669",
    "gid": "1003",
    "islink": "0",
    "linkpath": "",
    "name": "domains"
    "permission": "750",
    "showsize": "298k",
    "size": "304861",
    "truepath": "/domains",
    "uid": "admin",
    "sub_directories": "9",
    "files": "0"
    "api_flags": "6",
    "date": "1475246669",
    "gid": "mail",
    "islink": "0",
    "linkpath": "",
    "name": "imap"
    "permission": "770",
    "showsize": "4.32k",
    "size": "4428",
    "truepath": "/imap",
    "uid": "admin",
    "sub_directories": "5",
    "files": "0"
    "api_flags": "6",
    "date": "1475246669",
    "gid": "admin",
    "islink": "1",
    "linkpath": "./domains/",
    "name": "public_html"
    "permission": "777",
    "showsize": "0.04k",
    "size": "40",
    "truepath": "/public_html",
    "uid": "admin",
    "sub_directories": "1",
    "files": "10"
    "api_flags": "6",
    "date": "1463077334",
    "gid": "admin",
    "islink": "0",
    "linkpath": "",
    "name": "user_backups"
    "permission": "711",
    "showsize": "355.8M",
    "size": "373067818",
    "truepath": "/user_backups",
    "uid": "admin",
    "sub_directories": "0",
    "files": "2"
      "api_flags": "1",
      "date": "2",
      "gid": "3",
      "linkpath": "4",
      "name": "5",
      "permission": "6",
      "showsize": "7",
      "size": "8",
      "truepath": "9",
      "uid": "10"
    "current_page": "1",
    "total_pages": "2",
    "rows": "15",
    "ipp": "8"

sample json_files:

    "api_flags": "6",
    "date": "1430061718",
    "gid": "admin",
    "linkpath": "",
    "name": ".bashrc"
    "permission": "644",
    "showsize": "0.09k",
    "size": "89",
    "truepath": "/.bashrc",
    "uid": "admin"
    "current_page": "1",
    "total_pages": "1",
    "rows": "1",
    "ipp": "50"

sample json_all:

exactly the same as json_dirs, except each file/folder entry has one of:

type: "file"

type: "directory"

and where type=file won't provide extra files/sub_directories values.

json output for CMD_FILE_MANAGER new

For any CMD_FILE_MANAGER request, if you include:


with your GET or POST variables, any "dynamic" page output will be in json format, rather than html.

Will be either a success:

  "result": "/test/apache",
  "success": "File ownership reset"

or error:

  "error": "An Error Occurred",
  "result": "No files have been selected for upload."

for any CMD_FILE_MANAGER command that generates the dynamic output (aka: the template.html skin file).

Errors generate:

500 Internal Server Error

This is handy for ajax type calls to rename/copy files/folders, manage the clipboard, etc.. without needing to reload the whole page.

For the list of commands, see the CMD_API_FILE_MANAGER pages, as they're pretty much the same.

The main reason for making this json version was because the API might be disabled for a User, but the new skin requires ajax type requests of CMD_FILE_MANAGER for in-page file/folder management.

CMD_API_DB_USER list Users for all databases new

When creating a database in the GUI, there is a dropdown to use an existing User.

You could either call the CMD_API_DB_USER?db=user_dbname for each database to find all Users,

or you could use this new call to get all Users without all of the other work.




this will exclude the system account username from the list, even though it's probably on the DB.

Sampel return value:


Where it's a standard url.

The name's are:


while the values are just:


where 'fred' is the value you'd be passing for the username when creating a DB.

CustomBuild and DA to add linked IPs to httpd-vhosts.conf and nginx-vhosts.conf new

When you're on a LAN or have linked IPs, anything set to the server IP should also apply to those linked values.

For the normal templates, we used the already present MULTI_IP token.

But for the httpd-vhost.conf and nginx-vhosts.conf, they're not created by DA, only by CustomBuild, so we needed another swap.

The CustomBuild 2.0 build script release 1588 will have:



with new tokens:


where the dataskq will tokenize them.

The build script will also swap them to blank "" in case the dataskq doesn't support it yet.

To confirm the dataskq has it, type:

./dataskq h

and you should see:

./dataskq --linked-ips                                  :Output the server IP's linked IPs. Skip all other tasks.

in the output.

CustomBuild uses a modified version of that.

For apache and the httpd-vhosts.conf:

./dataskq --linked-ips=2

For nginx and the nginx-vhosts.conf:

./dataskq --linked-ips=3

and it will tokenize those files, respectively.

CMD_FILE_MANAGER div to have icons new

For the new skin, the Filemanager names will be displayed like:

 <i class="icon icon-pdf-small"></i>

so the skin.conf will have a new variable:


which tells DA which icons to support.

DA will set the given extension into the icon-XXX-small css value, eg:


allowing you to setup those css sprites, as desired.

For any extension not setup in the filemanager_icons list, DA will default to:


so ensure you have that css set for a generic file icon.

SNI for per-domain Dovecot SSL certificates (BETA) new

DEPRECATED!!! Use mail_sni instead of dovecot_sni:

mail_sni for dovecot and exim sni certificates


Feature related to this guide:

Allowing DA to create/manage per-domain SNI files for dovecot in:




Because dovecot requires the CA cert to be in the .cert file (that's how we'll do it anyway), if you have:



DA will auto generate:


for use in the dovecot config.

(Nginx also uses this .combined file)


The internal default is:


so set:


in your directadmin.conf, and restart DA.

Go to the:

User Level -> SSL Certificates

and if you currently have a pasted cert/key, simply hit "save" to regenerate the dovecot config for that domain.

Then setup the configs:

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


where it will use this template several times for each domain, to add SNI for:,, and


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

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

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


If you run:

./build dovecot_conf

If you need to add a custom file, create it


which will be set with the ./build dovecot_conf.

If you make SSL certificate changes in DA, DA may resave that file, so you'd need to:

./build rewrite_confs

again to reset any overrides.

If you custom file was extra, and not matching any existing file, then it should be fine in the custom/dovecot/conf.d/95.. method.


Related thread for doing the same thing in a different way:

zip_bin unzip_bin to allow zip/unzip binary override new

New directadmin.conf options:


both are null (unset) by default.

If the values are added to the directadmin.conf, they should hold the path to the zip/unzip binaries, eg:


adjusted as desired.

If the values are set to null (aka: not in the directadmin.conf at all), then DA will look for /usr/bin/zip else /usr/local/bin/zip to use for compression.

The purpose of this is to allow an override if in case you need to add a wrapper to unzip, in such cases as extraction of UTF-8 files, eg:


With unzip2 containing:

export LANG=en_US.UTF-8
exec /usr/bin/unzip $@
exit $?

json output for query:


Method: GET or POST

where path can be any folder name to search in.

Keep in mind that /imap and /Maildir can be huge, so don't search them if you don't need to.

optional value:




will search only for that type, and not show the other type.

Sample json output :

  "/domains/": "6188",
  "/domains/": "4424"
  "/domains/": "4096"
  "completed": "1",
  "path": "/"

If there is a MAJOR error, always first check for the "error" value, eg:

  "error": "Error with search",
  "result": "search length out of bounds: 2 < || > 128n"

where there are 3 arrays, files, directorties, and info.

The files/directories will have matched substring path and the file/folder size. (folders are usually 4096 bytes, but can be larger if there are many inodes inside)

The info array contains the path that was requested and a "completed" value.

The info['completed] value will be a number:

0 - all files/folders searched. If matched, it will be in the list.

1 - the size of the list maxed out before finishing.

2 - the maximum time passed before finishing.

3 - some error occurred and info['error'] will contain a string showing the error(s). (there could be multiple, one per line)

The max time is specified by the directadmin.conf option:


The max list size is specified by the directadmin.conf option:


difficult_password.php can be translated (LANG) new

The script:


previously couldn't be translated: it's output was what was shown.

This changes creates a new internal language pack at:


which can be translated into new languages.

The number codes in the .txt file are directly linked to the exit code of the script.

So exit code 0 (success) lines up with:

0=Password OK

Note that 1= uses %d characters, so this is the only special case where DA passes the integers into the translation class, so #1 must contain that string, eg:

1=Password is too short (%d). Use at least %d characters

the >2 don't matter, and you can add more if you want if you create a custom difficult_password.php file with higher exit codes.

If any codes are missing from the internal/difficult_password.txt, then scripts output is used instead.

But if the .txt for your language is completely missing, as per other internal/*.txt files, it will revert to the default language, usually the en folder.

Also, there will be an environmental variable:


which should show the User's current language, in case you'd instead want to control the scripts output for items not included in the internal/difficult_password.txt. to use RSASHA256 new

Changed the do_keygen() function in the script:


to use:


instead of:


but only if the script can see "RSASHA256" in the keygen, eg if:

/usr/sbin/dnssec-keygen -h 2>&1 | grep -c RSASHA256

is greater than 0.

If it's 0, then it's an older setup, so it will still use RSASHA1.

Ability to disable reload/HUP of apache/nginx after rotation new

New variable:


with internal default of 1, will control if DA sends an HUP signal to the pid file set in the directadmin.conf setting:


or if nginx=1, DA internally sets it to:


via the nginx_pid variable.

DA will read that value, and send a root call to:

kill(pid, SIGHUP);

If you do not wish to have the post-rotation send the HUP, you can set:


in your directadmin.conf, and the HUP won't be sent.

*** HOWEVER *** the HUP is sent for a reason.

This is used to re-open all rotated logs and bytes logs.

So if apache/nginx does not get the HUP, you may have logging issues.

If needed, immediately after that HUP is sent, the hook script:

is called if it exists.

So if you disable the HUP, you can take any other desired actions with that script. new

Custom hook script to run after a User has finished uploading a file in the CMD_FILE_MANAGER or CMD_API_FILE_MANAGER.

Would be useful if you'd like to run ClamAV on a given file after the upload.

Exit with a 0 code if checks are ok.

Exit with a non-zero result if there was an error.

Note that DA doesn't take any special actions on the files based on the output, other than throwing an error in the result upload message.

If you exit with a non-zero result, be sure to echo some text to clarify what the problem is.

Eg: if there was a virus (as decided by ClamAV), you might want to delete the uploaded file in your script, echo a notice about that, and exit 1;

Environmental variables:


ENSURE that you're fully quoting the $file variable.

This script does run as root and files can have spaces or other strange things.

It's your responsibility as a script writing to handle security validation.

ability to change favicon.ico new

If any request is made to DA for:

DA will send them the file at:


Where DOCSROOT is from:

cd /usr/local/directadmin/
./directadmin c | grep ^docsroot=



New directadmin.conf variable:


which allows you to specify some other file, such that you'd just place it in the images directory with a different name, and specify it in the directadmin.conf, and restart DA, eg:



Your "my_favicon.ico" would be safe from overwrites, since the enhanced skin does not include that file.

new "suspended" template directory for new Admin/Reseller accounts new

New directory:


Can be customized if copied to:


used for new Admin and Reseller accounts at:


It will include the same index.html that was previously generated by DA.

It will also now include an .htaccess file to prevent any caching of the html/htm/js/css files,

so that the moment a domain is unsuspended, the client's browser won't use the suspended cache. new

Custom scripts called if a DB User's password is set:



Environmental variables:


For if you exit with non-zero, DA will add your output and abort the change.

Accountability for domain setting change new

For any User making changes to their domain at:

User Level -> Domain Setup ->

Any changes in the file will now be logged, eg:

2016:11:23-20:10:01: fred: Domain php changed from 'ON' to 'OFF'
2016:11:23-20:11:14: fred: Domain bandwidth changed from 'unlimited' to '1002'

The accountability has been added as Users sometimes set their domain settings to a low bandwidth limit, which they forget about, causing their domain to be suspended, then deny they had made the change.

This offers Admins some recourse to verify when it was changed, and by who/IP, etc..

DNSSEC: automate adding subdomain's DS and NS records to parent zone new

Feature to automate this guide:

  1. If you're creating has is already signed, will be immediately keyed & signed.

  2. If you've just signed the DNSSEC zone, and exists on the server, if enabled DA will add the DS and NS records from to

Internal default value is enabled:


requires dnssec to be enabled.

Max upload size to use larger type new

Previously, the max size you could set was a "long" type with a max size of 2147483647 bytes (~2.1 gig)

Changed to use a "long long" type for a max value of 9223372036854775807, which is still something like 9,223,372,036 Gig..

I don't think you've got anything that large to upload but that's the new maximum limit that you can specify in the Admin Settings.

I've also set the new internal default for max upload size from 10 Meg to 500 Meg.

Enable direct_imap_backup by default new

After sufficient time in the wild without any major issues, I've now enable this feature:

Backup: direct_imap_backup for direct inclusion of imap folder into tar.gz

to be on by default.

This will greatly speed up backup times for server with a lot of email data, because it includes the imap folder directly into the final tar.gz file, rather than first copying it, then including the copy.

If you do not want it, you can turn it off by adding this to your directadmin.conf:


CMD_API_SUBDOMAIN?domain=all to show subdomains for all domains new

New option for CMD_API_SUBDOMAIN, if you specify domain=all with no other action, DA will create a URL encoded list of domains.

The value of each domain will be a list of subdomains, separated by colons.



You can add:




to the end of:


To get json output for a given ticket.

Another similar option added for DA 1.51.4


which sets which message to start with.

But if you set last_messages, this overrides the start_message, so pick one or the other.

The first message is start_message=0, so if there are 5 messages in the ticket, you'd use start_message=4.... or last_messages=1

The last_messages can be any positive int, and will show the last X number of messages in that ticket.

Can use a value higher than the number of messages, it won't hurt anything.

Output is very similar to CMD_API_TICKETS, but is json, and uses sub-arrays.

There is also an "info" array.

Note that each message index is it's array position, so the first message is item 0.

The info['start'] item will be the starting array position, and varies if you set last_messages.

The message[0]['user'] would be similar to the "to" value. If it's set to "creator" it's from the User.

    "from": "fred",
    "level": "user",
    "message": "Help me me me.",
    "name": "fred",
    "priority": "20",
    "status": "open",
    "subject": "This is my ticket!!",
    "time": "1479974898",
    "type": "ticket",
    "user": "creator"
    "from": "admin",
    "level": "admin",
    "message": "Yes, I can help.nPlease delete yourself.",
    "name": "admin",
    "priority": "none",
    "status": "open",
    "subject": "Re: This is my ticket!!",
    "time": "1479976365",
    "type": "reply",
    "user": "fred"
    "num_messages": "2",
    "start": "0"

Append Path for Reseller Backups (SKINS) new

Relating to the:

Admin Level -> Admin Backup/Transfer -> Backup : where : append path

option, a similar Reseller version is now available.




for both, add the JS function:

function set_custom_path()

and the 2 <tr> sections for the append path, in the same manner as the admin_backup.html and admin_backup_modify.html

CREATOR token in dns templates (eg: dns_a.conf) new

There might be cases where you'd want to know the creator of a User, when a new zone is added for a domain.

The CREATOR token has been added for the dns_a.conf style dns_*.conf templates.

Note that the value will not exist if a zone is created at the DNS Administration area, since it's not below a User.

edit_files.txt now requires root authentication to edit php.ini files new

The Admin Level "File Editor", when used to edit php.ini files, now has &secure=yes added to the end of each php.ini, so that root auth is required.

Because some php scripts are run as root, some settings would allow security risks in the php.ini for a non-root account.

SpamAssassin whitelist wildcards added to filter fixed

Relating to feature:

Add the SpamAssassin Whitelist to the domain filter to also whitelist SPAM Filter (TEMPLATES)

support now added for wildcards in the form:


which would use this in the filter:

or $sender_address contains ""
or $sender_address contains ""

Dovecot logging format update fixed

Dovecot 2.2.20 and older used format similar to this:

Aug 11 19:38:49 testserver dovecot[7384]: imap( Disconnected: Logged out bytes=132/9977

Versoins newer thatn 2.2.;0 use this:

Aug 11 19:38:49 testserver dovecot[775]: imap( Logged out in=1317 out=13696

Oct 15 19:38:49 testserver dovecot[123]: imap( Disconnected for inactivity in=588 out=1775

and the da-popb4smtp script isn't correctly catching this scenario.

directadmin_imap_backup without domains directory throws error fixed

Relating to this feature:

Backup: direct_imap_backup for direct inclusion of imap folder into tar.gz

If you use it, but do not select the "Domains Directory" option,

the inclusion of the "imap" folder will be referencing a wrong path because "-C /home/user" wasn't added to the end of the tar string before "imap" was added.

For the non-domains-directory option, when adding the "imap" folder, it will now add the full " -C /home/user imap" option.

Before, it was using (for example):

-C /home/admin/admin_backups/username backup imap

which isn't where it lives, hence the error.

The error generated is:

Error Compressing the backup file user.admin.username.tar.gz : /bin/tar:

imap: Cannot stat: No such file or directory

/bin/tar: Error exit delayed from previous errors

Two-Step auth cannot have spaces in "name=" for issuer fixed

If you change the name=DirectAdmin to a value with a space in the directadmin.conf, this will break the "issuer" variable in the QR code image.

The name will have all spaces replaced with underscores: _

backup_crons.list now url encoded fixed

When making a change to the backup_crons.list, the given entry will now be url encoded.

Report issue of an ftp username with a + character being replaced with a space when read in, as it would be url decoded.

Shouldn't affect anything unless you're manually editing that file.

created a new bug where the append_path was double encoded.

Found/fixed before this change made it to production.

Automatically detected and set Filemanager timezone fixed

Removed in DirectAdmin 1.666. to dump/restore databases fixed

Previous method was proposed to use the RENAME TABLE syntax,

but we ended up just using the full mysqldump followed by a mysql restore, as it's more reliable, and covers more things like SUPER PRIVILEGES functions, stored procedures, etc..

Affected files:




With the new script, a database can be easily renamed, eg:

./ user_old user_new

But it doesn't update the 'user' portion of user_new, so at the moment shouldn't be used to fully transfer a DB between Users without change the username,

so if you try and run:

./ user1_db user2_db

the DB will be moved, but it won't correct update the Users that need to access it. causing a bit of a cross-User mess.

The change_database_username.php is only for moving all Database that belong to a User, and not to move single DBs between Users.

ftp_list.php support ftp output with fewer fields fixed

The ftp_list.php was originally expecting ftp servers to output something like this:

-rwxr-xr-x   1 admin       admin              503 Mar    3  2016 file1.txt
-rw-r--r--     1 admin       admin            1028 Aug   8  2015 file2.txt
-rwxr--r--    1 admin       admin            5747 Sep 14  2012 file3.txt

But some ftp servers might output:

05-03-16  05:16PM            503 file1.txt
08-08-16  05:16PM           1028 file2.txt
09-14-16  05:16PM           5747 file3.txt

which confuses the parser.

The ftp_list.php has been chanegd to use the awk NF variable to count the number of fields.

Regardless of the number of fields, awk will now output only the last value.

Although we could use -1 with nftpls or --list-only with curl, because we want to parse out the directories, we need the full output.

output change for: "The request you've made cannot be executed because it does not exist in your authority level" fixed

Previously, the output for CMD_WRONG or CMD_API_WRONG would generate:

"The request you've made cannot be executed because it does not exist in your authority level"

in full html format, with an response header:

HTTP/1.1 200 OK

This change will now produce:

HTTP/1.1 403 Forbidden

and CMD_API calls will be in the proper url encoded format:

error=1&text=You cannot execute that command&details=The request you&#39ve made cannot be executed because it does not exist in your authority level

Filemanager to support 'multiple' file[] types fixed

The CMD_FILE_MANAGER and CMD_API_FILE_MANAGER can now upload multiple files via one file selection window, using, eg:

<input type=file name="file\[\]" size=40 multiple="multiple">

max user quota over 2TB fixed

DA was using an integer for passing around the size in meg.

Converted the type to be unsigned long long, to rule out any oversize issues.

The integer format cause large number to flip to negative due to the first bit being set.

nginx_redirect.conf template not to add location / for blank REDIRECT_PATH fixed

Changed the template to exclude the "location / {}" wrap around the rewrite, in the case that the REDIRECT_PATH is blank.

It's not required, since a location / redirect applies to the whole server{} anyway, and this also prevents duplcate location / sections.

Template now looks like:

rewrite ^/.*$ |REDIRECT_TO| |REDIRECT_TYPE|;
location |REDIRECT_PATH|/
  rewrite ^/.*$ |REDIRECT_TO| |REDIRECT_TYPE|;

Change to use use killall when removing User fixed

Previously, DA would manually get the list of processes, then kill each one before removing a User.

This wasn't entirely correct in that it's possible a new spawn would be created after the list was assembled.

Changed to use:

/usr/bin/killall -u 'username'

which handles things more cleanly.

preloaded .htaccess RedirectMatch being inserted into nginx.conf fixed

On User nginx.conf rewrites, DA will read a domain's .htaccess file for any RedirectMatch created by DA.

If present, the template:


is used, and those redirects are added to the nginx.conf.

But in some cases, the RedirectMatch settings set in the .htaccess file are not intended to be added in this way.

With this change, the behavior has been modified such that a:

RedirectMatch ...

line will only be used by DA in the nginx.conf if it has no leading space/tab characters.

So it must be ^RedirectMatch (for those who know regexes) in order to qualify to be inserted.

Most pre-loaded RedirectMatch .htaccess entries from CMSs typically are surrounded by <IfModule ...> checks, something DA doesn't do since we know mod_rewrite.c is always compiled into Apache.

tickets.list read efficiency fixed

In cases where the tickets.list file becomes over-sized due to Admin's not reading/deleting old messages, the read can be very slow.

Changes to the container class should make reading these oversized lists much quicker, as the file is now loaded in fully, and then sorted, instead of the previous default of reading a line, adding it to the end if it's not already present, and sorting.

The drawback with this faster method is that it does not check for duplicates, but they shouldn't (in theory) happen in the first place, so is a reasonable risk/benefit.

login.log missing some cases fixed

If you post to the CMD_LOGIN page directly from an external form, there would be 0 previous attempts, and the global login.hist file wouldn't have anything in it.

This meant that there were 0 previous attempts (usually loading the DA login page counts as 1 attempt, hence you usually always see 1).

The way it was written was the logging of the login only happened after on the first load of the true page, where the IP was cleared from the login.hist file.

But since a direct login didn't have an entry, there was no IP, so logging to the login.log or user login.hist file never happened.

Code has been changed around such that the actual writing of the session file in the same process as the CMD_LOGIN request will log to the login.log, user login.hist, and clear the global login.hist.

Then another case for authorized connections that do not have sessions (aka: API calls) will log to the login.log for each request, but still not to the login.hist file to prevent flooding.

All requests are still logged to the access log "2016-Nov-23.log" file (for example)

This has the added benefit that the login.hist is not checked/cleared for each CMD_ call. Only on the initial creation of the session, or a failed login, so should speed up connections somewhat.

check_subdomain_owner didn't allow subdomains on your own pointer fixed

If you had a pointer called, DA previously wouldn't let you create a domain stating it doesn't belong to you.

That because DA notices that /etc/virtual/ existed, yet wasn't in the User's domains.list file.

Change to the logic so it simply uses the /etc/virtual/domainowners file, so it can very quickly see who owns what.

nginx_redirect.conf template to end with (/|$) instead of / (TEMPLATE) fixed


location |REDIRECT_PATH|/

To be:

location ~ ^|REDIRECT_PATH|(/|$)

This will allow a redirect value of:


(previously didn't match, as it needed "/some/file.txt/")

as well as allowing adding "/folder" to allow:




which would redirect everything in /folder to the new location, while not matching:


Remove bad cron from list if crontab errored during addition fixed

If you create a cron with bad values, crontab would return an error, and not add them.

The issue was that DA had already saved the crontab.conf file so the bad cron was left in the list, causing confusion if other crons were attempted to be added.

Change is to remove a bad cron from the list anytime the crontab call returns a non-zero result.

Because apache will refuse to follow a link if the link owners does not match the destination owner, DA will now confirm the ownership of the link.

By default, the links are all created by root, so this isn't an issue.

But upon restore, because tar runs as the User, the link would likely be chowned to the User, thus the link doesn't match.

New internal directadmin.conf variable:


enabled by default. Set to 0 if you do not wish for DA to check this.

It will check:

  • icon
  • lang
  • lib
  • plugins
  • docs

but will not check the index.html, which should be reset nightly already.

If the value is not chowned to root, DA will not change the ownership, but full delete the old link, and re-create a new one pointing to the default path.

So if you're using some custom path, you'll likely want to disable this feature.

We fully delete it because we cannot trust any link value that the User could have set.

Note that the script:


already had a variable:


which is disabled by default.

It does roughly the same thing, but it's checks are not as strict as DA's so if you do need to use ENSURE_ROOT_LINKS=1, run it once, then disable it.

It's better to rely on ensure_root_awstats_links=1

Filtering on CMD_SELECT_USERS fixed

Relating to this report:

filter the location variable to prevent script injection.

Note that because of the check_referer=1 default value, there is no security threat if you have it enabled.


and just falls under the category of bug-fix.

session security improvements (SECURITY) fixed

Don't allow login/login-as if session is already logged in and referer is bad.

Don't allow logout if referer is a user controlled url within DA.

reload dns on monthly dnssec sign fixed

The monthly dnssec re-sign was not reloading named.

Create login key through login-as with login key fixed

You've got the ability to use a login key to connect to your Admin or Reseller (eg: "admin"), so you don't need to use your own password.

If you wish to use this login key to use the "login as" feature to login as a User (eg: "fred"),

but then want to create a login key for that User fred,

the "passwd" field in the form was incorrectly checking your admin login key against fred's current list, so the admin login key couldn't be used to create a login key for fred.

Fixed by correctly checking the correct list of keys.

ftp_list.php ftps required double leading forward slash fixed

When backing up to ftp with ftps, this uses curl.

The ftp you specify might be / or /admin_backups/, for example.

Previously, this would have been given to curl like:

/usr/local/bin/curl --config /home/tmp/1828.cfg --ftp-ssl -k --silent --show-error --list-only

But this actually wasn't correct, because the first slash is just a separator, making the ftp path just:


which is just relative to the login.

Because some ftp accounts are not chrooted, this might not be correct if they pass the ftp_path:


because if the default login path is /home/user, then curl would be trying to cd to:


which would not be correct.

The solution is to add a 2nd leading slash for curl in the script, so it looks like:

/usr/local/bin/curl --config /home/tmp/1828.cfg --ftp-ssl -k --silent --show-error --list-only

Awstats to run as User (SECURITY) * will increase User disk usage * fixed

Awstats will now be run as the User, and no longer as root.

This requires a conversion which will increase the User's used disk space, so it's go to ensure the Users are not maxed out, or the conversion will abort.

If there are any root-owned files that are 600 (not readable by the user), the copy will have issues, the diff won't exit with code 0, so the conversion would abort.

Conversion will copy, as User:

cp awstats awstats.user

and if that worked and "diff" returns code 0.

If not, everything is aborted, start are not run.

Next, run as the User:

mv awstats awstats.old

and if that works:

mv awstats.user awstats

Then the tricky part, handled by DA is to delete the root owned files (awstats.old), which requires root access.

DA does this very carefully, this is the task.queue command used to do it:

echo "action=delete&value=secure_disposal&user=${USER}&path=${STATS_DIR}.old" >> /usr/local/directadmin/data/task.queue

Related directadmin.conf option:



If you have Users on a different partition, like /home2

Because the awstats.old is simply "moved", moving cross-partition doesn't work, so you might need to clear the awstats.old folders manually, after you've confirmed the new awstats folder is computing stats correctly.


You may have realized that the User does not have read permissions on their apache logs in:


to get around this issue, the, using AWSTATS_MODE=1, will create a hardlink in:



/var/user_logs  root:root: 711
/var/user_logs/username  username:username 500
/var/user_logs/user/ root:root 644

so that the logs can be read, but not modified.

The link is removed after the end of the call.

If hard-links don't work for you, then you can use:


which does a full copy of each log, instead of using a hard-link.

Credit to Bartosz Kwitniewski for debugging and reporting the issue.

Last Updated: