Version 1.61.0

Released: 2020-05-20

Plugins can be package items new


The plugin manager will have the ability to enable/disable each plugin in the package system.

All plugins will be enabled by default, and only shut off on a per-user basis if they're set as such by the system.

A disabled plugin will not be able to run through CMD_PLUGINS/CMD_PLUGINS_ADMIN/CMD_PLUGINS_RESELLER


If both plugins_allow and plugins_deny are absent from a package, all plugins can run.

If plugins_allow exists (even if it's blank), the plugin must be present in order to run ("plugins_allow=" will block any plugin from running)

If plugins_deny exists, any plugin listed will be denied.

Having both plugins_allow and plugins_deny has no purpose, as plugins_allow will be the 100% deciding factor.

To clear a plugin for a given list pass [clear] to the value, eg:




will delete the given variables from the package/user.conf.

Again, setting "plugins_allow=" to an empty value does not delete the variable, it simply sets it as blank, thus no plugins can run.

Saving a package or user.conf change without passing the plugins_allow (or plugins_deny) will have no effect on the value currently set (in case some skins don't support it)


The packages pages, and listing of User settings will include an array, example:


                "custombuild": "custombuild",
                "hello_world": "hello_world"
        "plugins_deny" :

The right-side values in "all_plugins" will eventually be swapped for translatable strings, so please use the right-side value for display, and left-side index for passing to DA.


ECC ssl certificates (SKINS) new

Support for ECDSA (Elliptic Curve Digital Signature Algorithm) certificates for smaller key sizes and improved security.

New internal default:


where, if you wish to prevent Users from creating ECC certs, run:

cd /usr/local/directadmin
./directadmin set ecc_certificates 0
service directadmin restart

The calls to CMD_SSL are unaffected.

However, anywhere you can pass:


you can now also pass any of:


Enhanced: user/ssl.html

Swapped out the "keysize" select-box with |KEYSIZE|


Since the creates it's own keys, the new ECC key creation code will need to be run there.

To tell LE that we're trying to create an ECC cert, the provided san_config file will have it's usually (non-used) value:

default_keyfile = keyfile.pem

changed to:

default_keyfile = secp521r1.pem

or whichever new keysize algorithm is being requested.


The extra key-sizes are included in the new output, so this is backwards compatible (assuming your skin is ok without numbers)

        "2048": "",
        "4096": "",
        "prime256v1": "",
        "secp384r1": "select",
        "secp521r1": ""

However, a sibling array has been added in the same output, providing the standard "JSON Select" output, which includes nice name values:


                "text": "2048-bit",
                "value": "2048"
                "text": "4096-bit",
                "value": "4096"
                "text": " X9.62/SECG curve over a 256 bit prime field",
                "value": "prime256v1"
                "selected": "yes",
                "text": " NIST/SECG curve over a 384 bit prime field",
                "value": "secp384r1"
                "text": " NIST/SECG curve over a 521 bit prime field",
                "value": "secp521r1"


IP type global, for global shared spanning multiple resellers (SKINS)(BEHAVIOR) new

New IP option for a shared IP: global=yes

This will be set by the Admin in the Admin Level -> IP Manager for a given "shared" IP, so allow multiple Resellers to use it.

Once an IP is global, it can then be assigned to multiple Resellers, and will be treated as a shared IP.

Accessing the IP directly should go to the Admin's shared area, rather than the Reseller's shared area.

Replaces this guide:

The Reseller Level -> IP Manager will always hide the User count for global shared IPs.

The "Remove from Reseller" button will remove any global shared IP from all Reseller/Admin ip.list files,

but as before, does require the value to be 0 prior to DA allowing removal.

Setting a "status=free" IP to be global from the details page will automatically assign this IP to the current Admin, and convert the status to shared.


*** You can now remove the server IP from a Reseller ***

Admin Level -> IP Manager

When you select the server IP checkbox, if you previously clicked "Free from Reseller", it would throw an error.

The change is that this button is now allowed and DA will use the current drop-down Reseller selector to know which reseller to remove the server IP from.


To save changes, you can either use:

method: POST
global=yes|no  #ABSENCE of global=* counts as no, since it's a checkbox.


method: POST
set_global=<any text>
global=yes|no  #DIFFERENT FROM ABOVE in that absence of global implies yes.

You can pass global=no if you want to set it to no for the selected IPs. Enhanced does not do this.


When viewing the details of an IP:


if set, there will be:

"global": "yes"

the global value might not be present, which can be assumed set to "no".

If "yes", then there could be multiple Resellers/Admins managing this IP.

For ALL loads of the details of the IP, of any status, DA will scan all Reseller/Admin ip.list files to see who has this IP listed.

This array will be shown as:


this is mainly only for informational purposes, but can be handy in case the IP value is more than 0 and you're not sure who's on it (although the Show All Users page would be a giveaway)



Add form:

|*if global="yes"|
|*if status="shared"|
|*if status="free"|
|*if SHOW_GLBOAL="yes"|
<tr><td class=list_alt>|LANG_GLOBAL|</td><td class=list_alt>
<form class='mb0' action='CMD_IP_MANAGER_DETAILS' method='POST'>
<input type='hidden' name='action' value='global'>
<input type='hidden' name='ip' value='|ip|'>
<input type='checkbox' name='global' value='yes' |GLOBAL_CHECKED|> When enabled, this IP can be used by multiple Resellers
<input type='submit' value='|LANG_SAVE|' class='float_right'>




Direct CSF integration new

DirectAdmin already supports the CSF plugin, and already integrates with it using the set of scripts.

This feature will skip over the hook scripts, and instead make direct calls to csf.


csf -d


csf -dr

And get list of currently blocked IPs directly from CSF.


Optional comments to be set.

  1. The csf plugin must be installed

  2. The /usr/local/directadmin/scripts/custom/ must NOT exist (so as to not affect existing CSF integrations).

Once you have this copy of DA, to flip over to this integration, delete the files;


After testing confirms feature is functional, the script will be updated to not install the scripts/custom/*.sh files.


The BFM will use the /etc/csf/csf.deny to show the listing of blocked IPs and comments/dates.

The following files will not be related to the new method;


CSF does have it's own skip list.

The Brute Force Manager also has it's own skip list:


With integration, when adding an IP to the skip list through the BFM, DA will add it to both brute_skip.list and to CSF's csf.ignore.

Adding ranges in the format: will only be added to the BFM.

DA will not accept, but CSF will.

Adding the style ranges should be done in CSF, as even if DA decides to try and block an IP, the CSF csf.ignore would override the block, so the CSF allow has priority.

Listing all Skip value in DA will show the contents of both lists.

Deleting a value from DA ONLY deletes it from the skip.list, not from CSF's allow list (the value would remain showing).

There is room for improvement here, so it may come should there be sufficient demand.


Update: changed from csf.allow to csf.ignore:

Compile time: May 7 2020 at 18:04:30

Default Change: crypt_method=6 new

All new binaries from now will use the internal default crypt_method=6 value.

If needed, you can set this back to 1 for the older $1$ crypt format, but where possible, $6$ is preferred.

Default Change: systemd=-1 new

The internal default systemd variable will now be set to -1.

If you have a systemd value already physically set in your directadmin.conf, this set value will be used instead.

The systemd=-1 value will have DA auto-decide which setting to use, logic being:

if (systemd == -1)
        if (pathExists("/etc/systemd/system") && (pathExists("/bin/systemctl") || pathExists("/usr/bin/systemctl")))
                systemd = 1;//systemd
                systemd = 0;//init.d

The Linux static 64-bit binaries were already using systemd=-1 as the default, now all binaries will use it.

File Manager Delete to Trash instead of immediate removal (SKINS) new

New File Manager feature that moves files/folders to the ~/.trash directory when deleted, rather than immediately removing them from disk.

The related filemanager_disable_features number for "trash" is 65536.

"fm_settings" will now include:

"trash": "1"
"trash": "0"

depending if the feature is enabled or not.

New directadmin.conf option, internal default:


which indicates the age of days a file before being deleted.

For folders, the last modified time of a folder must be >= 30 days old for it to be traversed.

Valid values:

-1 never auto-purge
0, immediately purge if found

up to a max of 10000 days before being purged.


When deleting a file/folder, if the "trash=yes" option is included, the item will be move from /some/path.txt to /.trash/files//path.txt

This will follow the trash specification:

If there is a duplicate file in .trash/files/path.txt, the trash file is instead moved to .trash/files/path.txt.1

A related info file will be created in .trash/info/path.txt.trashinfo or (.trash/info/path.txt.1.trashinfo depending on how it was saved).

The info file contains the Path and DeletionDate, which points to the original location (excludng any .1), and when it was deleted.

Similar behavior for directories, where deleting a dir to trash moves it to .trash/files/dir or .trash/files/dir.1 if it already exists.

Each delete-to-trash operation creates it's own instance. Deleting the same file over and over again creates a new copy, each of which can be restored.

When viewing /.trash in the Filemanager, DA will not show you what's actually there, but the listing of "Path" items file/dir names from the .trashinfo file.

There may be duplicates here.


Restoring the /.trash/files/* data simply renames it to where it came from.

If any parent folder is missing, the path will be created as 755.

The value given to DA to do the restore MUST be the /.trash/files/file.txt.1 path, and not the "visible" /.trash/file.txt, as that does not exist.

DA needs to reference the data file, which does not always match the original filename, as there may be duplicates.

Per file can be:


For mass restore, very similar to the POST Delete button in the Filemanager, except you can pass:




along with each select0=/.trash/files/file.txt you want to restore, and "path=/.trash"


Any tally, "all" or per-User, which affects disk usage, eg:


will run the fully trash clean on either all Users (value=all) or on just that User (value=fred&type=user|reseller) PRIOR to running the repquota dump.

This means that the trash is cleared before DA reads the total disk usage used, so usage shown is kept correct.

echo 'action=delete&value=trash' >> /usr/local/directadmin/data/task.queue
echo 'action=delete&value=trash&username=fred' >> /usr/local/directadmin/data/task.queue

the fm_settings and api_flags per-file will include new info for "trash=0|1".

The api_flags for a given file will let you know if that file is in the trash, so if it should have a "restore" button beside it.

The mask for trash is:

#define FM_F_TRASH 65536

which can be used both with filemanager_disable_features but also binary-added into the api_flags.

The call to:


is "fake" in that is returns, for example (a directory called "delete"),

where the index is what should be shown, but the "truepath" is where all actions and restores should be sent from.

So the restore must use the truepath, not the index.

                "api_flags": "65542",
                "date": "1589524902",
                "gid": "evoboss",
                "islink": "0",
                "linkpath": "",
                "name": "delete",
                "permission": "775",
                "showsize": "8.00k",
                "size": "8192",
                "truepath": "/.trash/files/delete",
                "uid": "evoboss",
                "type": "directory",
                "mtime": "1589524873",
                "atime": "1589524902",
                "sub_directories": "-1",
                "files": "0"
                "api_flags": "65542",
                "date": "1589525934",
                "gid": "evoboss",
                "islink": "0",
                "linkpath": "",
                "name": "delete.1",
                "permission": "775",
                "showsize": "8.00k",
                "size": "8192",
                "truepath": "/.trash/files/delete.1",
                "uid": "evoboss",
                "type": "directory",
                "mtime": "1589524873",
                "atime": "1589525935",
                "sub_directories": "-1",
                "files": "0"


Also, we should only show the "restore" button for the files/foldres in the /.trash listing.

Assuming the truepath is used to navigate into delete directories (which is allowed), then should not see a restore button on, for example:


but they can delete the test.html, should they wish to clear it from the trash ahead of time. (use the truepath, but here it should match).

The items in the listing of /.trash should also have a delete button, where DA will notice is a trash-deleted-item, and will also cleanup the /.trash/info/delete.trashinfo file as well.


Enhanced/Power User:

New IMG_TRASH icon for the /.trash directory.




Ability to disable Custom Httpd syntax checking new

It was found that an openlitespeed box with nearly 7000 domain (plus sub-domains), the syntax check call:

/usr/local/lsws/bin/openlitespeed -t

was taking upwards of 30 seconds.

New internal default option for the directadmin.conf:


where you can disable the syntax check when viewing the custom tokens for a give domain, by running:

cd /usr/local/directadmin
./directadmin set custom_httpd_syntax_check 0
service directadmin restart


Option to include PID in each log entry new

Sometimes trying to step through the logs can be confusing if there are multiple processes logging at the same time.

This option includes the PID number as [12345] to each log, just after the timestamp.

Internal directadmin.conf default:


to enable:

cd /usr/local/directadmin

./directadmin set pid_to_logs 1

service directadmin restart


Custom Domain Items: admin_restore_only=yes|no so Users cannot set a value via restore new

The Custom Domain Items feature:

Custom Domain Items (SKINS)(LANG)

now has a new option which can be added to any value inside:


where you can append:


So that when restoring the custom domain items values from the User's backup,

the backup/ custom value will only be restored if:

  1. admin_restore_only=yes is not present in the global config

  2. or admin_restore_only=yes is present in a line in the global config, and the restore is being done via the Admin Level.

When an item has admin_restore_only=yes set, neither triggerig the restore from Reseller Level nor User Level will restore the given custom item line from the domain.conf in the backup.


GUI: Subdomain php-version selection, DocumentRoot override new

New GUI for both Enhanced and Evolution to lets Users pick a new DocumentRoot for either public_html/private_html/both, and also have the given subdomain use a different php version.

With Enhanced, this URL will have a DocumentRoot column:


which will show any changes to the given subdomain.

When that info is clicked, you'll end up here:



To make a change to the public_html or private_html document root value in their respective VirtualHosts:


method: POST

one or both of:

  • public_html=/domains/
  • private_html=/domains/

where their values must be below the /domains/ directory, and must exist.

To reset the values to the default, pass the public_html or private_html (or both) but set an empty "" value.

Not passing the variable at all will not affect that value, so you can change one without changing the other.


A new back-end has been created for the subdomain php version selector:

Subdomain: per-sub php version selection

The back-end is a single POST:

method: POST




where, 1|2|3|4 and subname should be swapped with their respective values.



Removed 'default' skin new

The "default" skin will no longer be included in the update packs.

The skin, if you have it on your system, will remain in place, but new installs will not have it.

Prevent creating Reseller/User named "admin" new

Extra check when creating a Reseller or User, where the username must not be "admin".

To avoid confusion, only an Admin account may be called "admin".

FTP: Immediate /etc/pureftpd.pdb updates on password change new

Previously, the password was changed in the /etc/proftpd.passwd file, and a task.queue call was pushed to request updating the /etc/pureftpd.pdb file by the dataskq.

This could take up to 60 seconds.

This change simply reworks the FTP task.queue trigger function to issue the /etc/pureftpd.pdb rebuild on the fly.

All instances of the /etc/proftpd.passwd.lock being cleared have been moved to just after this trigger, so the lock will be held a few moments longer.

However, the locking system does allow for time to pass as it waits for the lock to clear before giving up, so this shouldn't be an issue.


Nginx: CUSTOM2: ability to insert into any location path, without password protection or redirect new

This applies to Nginx only installs (not nginx_apache proxy).

For nginx, the CUSTOM2 token lives within the "location /" sections of the per-user nginx.conf files.

These sections appear for either password protected directories, or the site redirection feature,

the nginx_protected_directory.conf and nginx_redirect.conf templates, respectively.

However, if you're not using either of these features, there won't be a be a location section, thus nowhere to put the CUSTOM2 token.

This feature, creates a new template:


which has littler more than a location for the given path, the |CUSTOM2| token, and the |NGINX_PHP_CONF| file:

location |LOCATION_PATH|/

DirectAdmin will read a new file:


which will have something like:


where it's 1 line per path, with the path being followed by = for possible future use.

Note: any variables/values you set in there will also get added to this areas tokens list (but do not carry out to global tokens).

For example, you could add:


which then makes the |EAT| token available,set to "apple", when tokenizing the nginx_blank.conf for this one path.

If there are any password protected directories, or path redirections on the same path name, this nginx_blank.conf will not be used, as those 2 templates DO have the CUSTOM2 token present inside.

The extra location sections will be inserted into the EXTRA_LOCATIONS token.

Possible GUI to manage the to follow.


Exim: Include rejectlog in account "In / Out" logs new

New internal variable:


If this /var/log/exim/rejectlog log exists, DirectAdmin will now include it's output along with the other inbound log entries from the mainlog for the given

exigrep is called twice in 1 call, separated by a semi colon, where DA reads in the output from both.

DA then loads the full output into a ListFile container, where it's sorted, as both logs share the same timestamp format.

This will allow Users to view any rejections only visible in the rejectlog, as opposed to only seeing the inbound logs from the mainlog.

Tokenizer: option to not clear the environment: tokenizer_clear_env=1 for scripts new

New internal directadmin.conf value:


where you can disable it with:


causing the Tokenizer not to wipe the environment before adding new values.

This happens when the Tokenizer::runScript function is called, to process:

<?php echo "hi"; ?>

Type embedded calls.. anywhere.. (skins or templates, etc).

So setting this to 0 will affect everything, just be conscious of that.


The default, which does clear the entire env, has been changed to restore the env after being wiped out.

So the env vars from before the tokenizer runs a script, will be restored after the script is done.

Thus the only benefit to the feature:


is to pass any pre-script env vars to the script (aside from anything that is set/ovewritten for the script, as before, which will still be set)


I've also added a new CMD call so you can see all current ENV vars.. if there are any or not:


Generates a JSON sub-array called "environment" holding all current "environ[]" vars from the system.

You'll also get a top-level json int value:

"tokenizer_clear_env": 0

if it's on (1) or not (0).

Note that this is one of the first times the json output will generate an integer without "quotes".

Be sure your json parser is correctly handling int values.


Messages: "E-Mail Only" to use same encoding as skin (SKINS) new

If you've got a UTF-8 skin, and are sending an "email only" message to one or more Users, unless you were to specify:

|?HEADER=Content-Type: text/plain; charset=UTF-8|

the message would not get the UTF-8 encoding, and the resulting email might be garbled if you were using special characters.

This changes does 2 things:

  1. Includes:
<input type=hidden name=encoding value="|LANG_ENCODING|">

where |LANG_ENCODING| is replaced with the encoding, we'll use UTF-8 in the examples below,

in all email-only messaging forms, so DA knows what the encoding it.

Unfortunately, the POST does not include sufficient encoding information from the form to do this automatically, hence changes are needed.

With that, should the Content-Type header not already be set (via |?HEADER=), then DA will add headers

Content-Type: text/plain; charset=UTF-8

MIME-Version: 1.0

  1. The E-Mail only form now supports up to 4 headers:


which you can set with, eg:

|?HEADER3=Your: header|

in the message.

Note that these e-mail only messages continue to support <html> formatting, as before, simply by starting the message with:


which will now also include the UTF-8 charset, eg:

Content-Type: text/html; charset=UTF-8


Enhanced skins changes in:





which includes:

<input type=hidden name=encoding value="|LANG_ENCODING|">

in the submission form.



Custom Hooks:, (HOOKS) new

2 new hook options:,

can be created in any valid hook script location:

Sub-locations for all script hooks (HOOKS)(PLUGINS)

and will be triggered when a Login Key is either being created or modified, or if a One-Time Login Hash URL is being created (as it essentially creates a special login key to do this)

Values passed to the scripts:

keyname=<name of the login key>
is_login_url=0|1  #if 1 we're creating the one-time login has url. 0 means a normal login key.
modifying=0|1  #new or modifying an old one
key=<secret key>  #only applies to creations, it's the plaintext/raw plassword for the key to work with.
expiry_timestamp=unix timestamp for the current expiry of the key. 0 means never.
allow=list of CMDs allowed, separated by | character
deny=list of CMDs denied, separated by | character
ips=list of ips allowed, separated by | character.
login_keys_notify_on_creation=0|1|2  #based on the directadmin.conf setting, altered by DA at run-time, depending on if a notice should be sent for this call.


CMD_IP_MANAGER?json=yes to include extra IP info new

The new global shared IP type will now get it's own column on the CMD_IP_MANAGER table (both GUI and json)

When json=yes is passed, an extra column called "extra" will be added to the end, containing a json sub-array of info, currently with "creators", a json array of Admins/Resellers who are on this IP.

If an IP has zero creators no it, the "creators" array will be absent.


                "ip": "",
                "status": "shared",
                "global": "yes",
                "reseller": "admin",
                "users": "0",
                "nameserver": "",
                "netmask": "",


Package: rename & copy new

End-point for renaming or copying a package, without needing to pass all existing information.

Both will use the respective package levels:












BubbleWrap jail for ssh/crons (BETA)(TEMPLATES)(SKINS) new

For CentOS 7 and up, CustomBuild now supports:

./build bubblewrap
./build jailshell

to install /usr/bin/jailshell.

DirectAdmin can make use of this with a new value (set to 0 by default)

/usr/local/directadmin/directadmin set jail 1
service directadmin restart

which enables the package/reseller/user.conf options for "jail=ON/OFF"

Any sshd related changes will save /usr/bin/jailshell (if exists) to that User's shell in /etc/passwd.

Any cron changes will save SHELL=/usr/bin/jailshell (if exists) in that User's crontab.

ssh does not need to be enabled to enable jail for the given User.

Alternatively, you can set:

/usr/local/directadmin/directadmin set jail 2
service directadmin restart

so that regardless of any package/reseller/user.conf settings, jail is ALWAYS enabled, and will always be set for ssh/cron when saved.

Note this does not work on all OSs and some VPSs due to either the kernel not supporting it, or for VPSs due to the fact the VPS may already be using this jailing technology, so it cannot be double-bubbled.


exim is within the jail and is not yet able to read the exim.conf.

We're working on a solution.



Note that this is entirely based on the directadmin.conf.

CustomBuild will need to set jail=1 upon ./build jailshell and restart DA.

Admins can bump this up to level 2 if desired.


JAIL_TRUE_FALSE= "" || "jail=true"   #where if it's false, it's blank.  jail=true is in the token only if it's enabled for this User.



to add |JAIL_TRUE_FALSE| just after the |DOMAIN|.ini in the 2x FCGIWrapper lines.







jail chroot sandbox


Possible design change: da.conf:jail=1 is a feature, not a requirement. Aside from =2, some way to prevent Resellers from disabling it.

Not applicable with =2, but an Admin may want more control over forcing Resellers/Users, with control over on/off on a per-account basis.

gzip for json new

Many areas that generate json output will now have that output gzipped.

Instead of sending the data as the container is traversed, one large json string is generated internally, followed by standard gzip chunked encoding.

The filesizes should but about 1/10 the size, speeding things up, even with the extra internal memory usage (json data is typically not that huge, so no major issue)

Feature Sets new

Packages already give control over limits and other features.

However, it does not (easily) allow for control over which CMD values are allowed in the account.

New feature called "Feature Sets" which allow easy selection of various sets, which can be added together.


The user.conf file and User packages will have a new optional variable:


where the values on the right are only an example (no default value).

Submitting a blank "features_set=" for packages/user customization will delete the variable from the file.

The values must be colon separated, no spaces, and must exist.

Any bad/missing sets are ignored without errors being thrown.


All feature sets will live here:


where each set is a directory.

Each set directory will have a file:


which acts like the usual commands.allow files (no commands.deny at the moment).

Custom sets with different names can be added or current ones can be overridden by creating a copy in a custom folder:



dns_only : Users can control their dns records.

email_only: All E-Mail related functions

tickets: Allows use of the Message and Ticket System

view_domain: A base set so User can view the domain overview, stats, change passwords and logout (this set is recommended, but not required if it's API only)


will include a top-level array called "feature_sets", with each element index being the code name of the feature set.

The value of this code name is another array containing "name" which is the display name, and "checked" : "yes"|"no" if that items has been checked.

                "checked": "yes",
                "name": "DNS-Only"
                "checked": "no",
                "name": "E-Mail-Only"
                "checked": "yes",
                "name": "Tickets/Messages"
                "checked": "yes",
                "name": "Core Commands"


Include MySQL Queries in high-load output new

When DA triggers a high-load notice to the Admins on the box, the scripts/ will now include the output of the query:


in case the load is caused by mysqld.

The output is only generated if:

  • mysqld is running

  • the conf/mysql.conf is has host=localhost or not host at all (default sets to localhost)

as a the queries on a remote mysqld server is not likely relevant in the output.

directadmin.conf: block_cracking_variables_conf=/etc/exim.blockcracking/variables.conf new

New directadmin.conf internal variable:


Note that DirectAdmin will also append .custom to the end of the value in some cases, meaning the variable will affect both the default and a custom override file. from GUI (SKINS) new

New button in the Admin Level >> Licenses/Updates page:

"Update License Auto"

in addition to the existing "Update License" button.

The new auto button will make a call to the "./ auto" script.

This is very useful if your License ID or Client ID has changed, DA will automatically figure out which license should be downloaded and will do so automatically.

No need to figure out your new LID/UID numbers.



new button, next to the existing "Update License" button to call:


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

although,if you already have root access, you could just run:

/usr/local/directadmin/scripts/ auto



Lang: Internal texts for Update class new

The Update class was previously an Admin-only class, and internal translations never occurred on it.

With the introduction of the "Verify License" button for clients, this was causing confusion.

This change adds a new internal text translation file at:


which can be copied from "en" to the language of your choice, in the skin directory you're most likely using, eg:


assuming "nl" is what you're translating into. Adjust paths as needed.


Changes were also made to strings in preparation for gettext() translations, already in progress.

T25834 at domains/ time new

Hook which can be triggered after the logs from /var/log/httpd/domains/ are copied over to ~/domains/ (path variations depending on settings will apply)

Create it here, not the new path naming method:


where is renamed to your own name/purpose for the script.

This allows multiple hooks to be called on the same trigger.

Script is called after the logs are rotated into the User logs path, but before the web logs are actually deleted/truncated.

As this script runs as root, it's not recommended to work on any data in the User's home path, or anywhere a User has write access.

If you're using this to save a copy, we recommend coyping it direct from the /var/log/httpd/domains/ path to another non-User-writeable path.

Use any su trickery to run as the User as your own risk 😃

Variables passed to the hook:

dest=/home/fred/domains/     (or /home/fred/domains/, etc..)
files="/home/fred/domains/ /home/fred/domains/"     #this is all one string, without quotes, used during tar call.
file=/var/log/httpd/domains/  (or #note that this is not the file being worked one, but where the .1 file came from.
access_log=/var/log/httpd/domains/    (or or, can vary depending on settings)
error_log=/var/log/httpd/domains/  (or

php_mail_log_dir=|HOME|/.php (TEMPLATES) new

New internal value:


which is unset/missing.

If you add any string, even an empty value like "php_mail_log_dir=" this will be used (don't add an empty value)

This feature allows you to override the /home/user/.php folder to use some other location, in the event your clients have a habit of deleting their logs, eg:


which would be the same as the default we already have now.


Available tokens for this value:


So if you wanted to store them elsewhere, like:


You could use:


Note that DirectAdmin assumes the directory has permission to be created while running as the User.

As such, the parent folder must exist and be controllable by the User ahead of time.

In the above example, this would not be true, so using a hook like:



mkdir -p $D
chown $username:apache $D
chmod 770 $D
exit 0;

would be needed.

Post-cleanup might be required too:


rm -f $D
exit 0;

and chmod your scripts to 700.


Similar to permissions, rotaton of the php-mail.log file will be done as the User, hence the importance of this directory being writable by the User.

  • openlitespeed_vhost.conf
  • php-cron.ini
  • php-fpm.conf
  • unit_application.json
  • user_virtual_host.conf
  • virtual_host2.conf
  • virtual_host2_secure.conf
  • virtual_host2_sub.conf
  • virtual_host2_secure_sub.conf






Rspamd Controller WebUI (SKINS) new

Since rspamd can be installed:

Rspamd: binary replacement of SpamAssassin (TEMPLATES)

and rspamd come with a nice controller socket, allowing a WebUI:

this feature will have DirectAdmin acts as a proxy to this socket.

This is an Admin-Only call, and the socket is proxied from the command:


note, as all included files with this call are relative to it, it needs to be treated as a directory.

Accessing "CMD_RSPAMD_SOCK" without the trailig / charcter will issue a redirect to CMD_RSPAMD_SOCK/

All files below this path, eg:


are sent to the socket as http://localhost/img/rspamd_logo_navbar.png

Allowing a the rspamd WebUI to work through DirectAdmin's connection on 2222.


The button for the rspamd controller is in the Mail Queue Admin (Enhanced skin):


Only change is an added button:

<input type=button onclick="location.href='/CMD_RSPAMD_SOCK/'" value='Rspamd Controller'>

where the HAVE_RPSAMD_CONTROLLER_SOCK token will be yes or no.



will include:


to indicate if the button is available.


you can also just call:


and if the feature is not available, it will throw a standard json error.


Netdata Metrics new

Netdata Metrics for realtime, highly detailed server information.

cd /usr/local/directadmin/custombuild
./build netdata

The new command in DA will be:


where DA is nothing more than a proxy over to:


This is an Admin Level only command.

Evolution: Admin Level -> Admin Tools -> Netdata server metrics

Enhanced: Admin Level -> Complete Usage Stats -> Netdata Controller


Ensure the sock exists:


if not, try a full stop/start of the service:

service netdata stop

service netdata start

Once the sock exists, in DA, issue a full ctrl-F5 to ensure the latest checks have been done for the service.

Forwarders: other create options new

When creating a forwarder with the standard method:

method: POST

This change lets you use other method of creation:

method: POST
method: POST
action=create -->,

Where the #1 and #2 must not have "domain" nor "DOMAIN" passed to them.

The domain will be taken from the user variable.

For #3 it must not have "domain", nor "DOMAIN", nor "user" passed else it will not trigger.

No more AllowUsers in sshd_config new

Since it's not really needed and only adds extra complexity to the system, DirectAdmin will now have checks to see if there are any AllowUsers lines in the sshd_config file.

If there are, the previous logic is used where Users are added/removed from the sshd_config anytime ssh is enabled/disabled for this User.

However, if there are zero lines that start with AllowUsers, then DA assumes they will not be used, and no future AllowUsers lines will be added.

TODO: The will also be changed to only add AllowUsers root, AllowUsers admin if there are already AllowUsers entries in this file.

This must be done at or after the time of the full DA release, so that the live still adds them for the previous DA version.

per-User mail_partition override in user.conf new

If you only want a select number of Users to use the mail_partition, or inversely, which for most to use it, and some not, you can set the directadmin.conf mail_partition as desired for all (set or blank),

and then override it for any User that needs it by setting some other partition in their user.conf file.

I you have a blank directadmin.conf mail_partition, then you can add mail_partition=/mail to the user.conf for the User to use this other path.

If you have mail_partition=/mail set in the directadmin.conf, you can add mail_partition=/home to Users who you will want to use their ~/ home directory for email.


doveadmn expunge to purge directories new

On the "E-Mail Accounts" page, the "Purge From X" button (to purge from spmabox, imap folders, inbox), used to hunt in the given paths for files that match, to be removed.

This change will instead use the "doveadmn expunge" tool instead.

For the "Imap Folders" option, the command "doveadmin mailbox list" is used for the given account to obtain the list of folders to purge.

Also includes the "purge_spam_days" option, run during the tally to clear the spambox for virtual email accounts.


Block search engines: 2222/robots.txt new

Block all search engines from showing/caching your 2222 login page.

Any call to /robots.txt on your DirectAdmin port will return:

User-agent: *

Disallow: /


Mailing Lists: ability to set sender (TEMPLATES)(SKINS) new

The majordomo 'sender' value can now be set in the List settings area.

The e-mail address specified must be on the same domain, but "owner-name" is still allowed.



<input type=text size=16 name=sender value="|sender|">

If the "sender" is not passed, there is an internal default of owner-|NAME| set, so it will continue as it did before.



sender              =   owner-|NAME|

to be:

sender              =   |sender|

EVO1887 will restart DA on valid license.key new

Since a valid license.key download will almost always require directadmin to be restarted, calling the script will now issue a task.queue.cb request for immediate restart of DA.

If you missed it, the new simple/recommended way to get a new license from ssh:

/usr/local/directadmin/scripts/ auto

Quick/minimal json listing of email accounts new

If you only need the list of email accounts, you can now append &quick=yes:


Which returns an "email" array:

"emails" :

SSO: Client IP override new

The Webmail and Database one-click login tool will always use the IP of the currently logged in User, to set into the login token.

This change allows scripts to automate a redirect for a client, so they can login without a password.

The script must pass a header with a valid IP, eg:


in the httpd request. Again, this is a header, not a GET, nor a POST value.


Resend Welcome E-Mail: sets "Return-Path" to Reseller's email new

When using the "Resend Welcome E-Mail" tool, to resend the info or reset a new password, the Return-Path value will now use the same Reply-To header.

If needed, utf8_encode_from_to=1 will encode it.

The value is passed to exim via the -f flag.


logs_history_as_nobody new

Optional setting, off by default, which can save your User's logs folder and contents as "nobody", preventing them from deleting them from:


To enable:

/usr/local/directadmin/directadmin set logs_history_as_nobody 1
service directadmin restart

UPDATE: use at least ./directadmin o:

Compile time: May 6 2020 at 16:41:18

Allow style masks in Login Keys new

The IP lists for Login Keys now permit the use of standardized subnet masks, eg:

which would allow the login from any 1.2.3.x IP address. (Can be any sized bit /mask, doesn't not need to be per 255 block.

This also works for IPv6 addresses and /128 masks.

All types are run through inet_pton, and IPv4's are internally prefixed with ::FFFF:, so they're also worked on in an IPv6 space.

Compile time: May 3 2020 at 21:29:29


directadmin.conf cleanup: rely on internal defaults (TEMPLATES) new

The directadmin.conf is basically a means to override the internal defaults with custom values.

The only time a value is actually needed in the directadmin.conf is to use something else.

Many values that were in the template:


were redundant, in that they're values that would really never, or rarely change.

Their presence really only added more information than was needed.

The values that remain are:

  1. Value that are specific to this server, eg:

servername, ns1, ns2

  1. Values that will be set on new installs, but should not alter existing installs.

These are overrides that are different than the internal defaults.

Anything else was removed, relying on the internal default.


A new feature is able to show the "diff' from the internal defaults vs the loaded directadmin.conf:


where the array "directadmin_conf_diff" will show the values which differ.

Keep in mind that some of these values are internally generated/modified, so not all are entirely relevant.

For example, with OLS, we get: "apachecert": "/usr/local/lsws/ssl.crt/server.crt", which is internally modified.

Compile time: May 4 2020 at 17:36:20

Block Domain Rename new

Ability to block Users from renaming their domains.

Internal default:


To block the ability, set:

/usr/local/directadmin/directadmin set users_can_rename_domains 0
service directadmin restart

This has 2 effects:

  1. Fully blocks the CMD_CHANGE_DOMAIN and CMD_API_CHANGE_DOMAIN calls.

  2. Extra check where the call to execute the rename would fail (it shouldn't ever get this far)


You can override this setting for any User by adding:


to their user.conf, if you wish to have a User be exempt from whatever the directadmin.conf is set to.

Details for a given month in User History does not show last day fixed

Sample URL that doesn't show the last day of the month:


Only goes up the 2nd last day of the month.

The tally on the 1st of the month (just before the reset) is adding then entry for the wrong date.. in the above case, January 1st, 2013 (yes, 2013), which is doubly wrong.

Bug was caused by the final tally using the 1st day of the month in the bandwidth.tally.cache file, eg:

2020 01 30=...

2020 02 01=...

and the backup to the history directory filtered out the "02" month as it was for the "01" log.

Changed the read of the bandwidth.tally to always subtract 6 hours from the last "time" read, since all tallies should be done by then.

That would put the day to the previous date, giving us the correct final line for the bandwidth.tally.cache.. so the history is correctly stored.

Unfortunately, the old missing data cannot be recovered.

These binaries would need to be in place for the final tally of the month (at 12:10 am the first day of the next month, to compute the final day)


Do not continue to increased failed attempts/unauth access count, if they're blacklisted fixed

The 2222 failed attempts and unauthorized attempts counters were trying to count up, after the IP was already added to the ip_blacklist.

The issue was that the moment the IP is added to the blacklist, the data/admin/ip_access/ folder is deleted so that the IP can be cleared from the ip_blacklist and continue to login one the admin clears it, or the removal time kicks in.

This should only be a cosmetic error causing extra log entries, eg:

2020:02:22-12:55:57: Error opening ./data/admin/ip_access/ for appending count: No such file or directory

which should be caused by the ip_blacklist having the already, added in the same process just before DA tries to increase the count.. but the directory is gone, hence the error.

Future requests should blocked by the ip_blacklist, so it shouldn't get this far again.

Also bug with Evolution where it needs to check for the header:

X-DirectAdmin: blacklisted

since the block output is generated before anything is parsed, so the json=yes in the POST or headers is fully ignored, and the output isn't json (ever).

Thus the header response is the only reliable source of knowing of the blacklisted value, so it gives the proper message (bug is that it says "wrong user/pass" instead of blacklisted.)


CMD_MASTER_LOGIN: don't show full list if more than 500 Users fixed

New internal directadmin.conf value:


where the Evolution call to:


will get a json error:

        "error": "List of Users is too long",
        "result": "master_login_max_list: 23003 > 500"

so it knows not to show the raw list, and rely solely on the search, while the creator types in the desired Username.


IMG request were always gzip encoded fixed

Bug where da_gzip=0 was being ignored for IMG_* requests.

Fixed to send in gzip, only if da_gzip=1 and if the client's request supports it.

direct_crons allow LANG and LC_ALL fixed

Extra headers allowed in the crontab:



to prevent cron read errors.


Plugin: hooks/*_(img|txt).html run as calling User fixed

The tokenizing process for the hook files in:

/usr/local/directadmin/plugins/PLUG/hooks/user_txt.html (for example)

will now be able to run the embedded scripting, with the usual format, eg:

echo "<a href=''>Plugin</a>";


Backup/Restore: skin_customizations fixed

Backup and Restore Reseller skin_customizations directory.

Demo accounts using demo docsroot but not skin value fixed

Bug where a demo login is correctly setting the "docsroot" value for the demo skin, however the internally loaded "skin=" variable was not being set to the skin name from the demodocsroot variable.

If you had the docsroot and demodocsroot both set to the same value, you would not likely be affected,

but if you have enhanced for docsroot and evo for demodocsroot, the default "skin" value would have ended up as "enhanced" using the evolution path.

Fix: while loading the default demo variables, also fill the "skin=" variable based on the demodocsroot value.

Trigger on more cases fixed

Previously, the was only triggered if the restore of the current tar.gz failed during the actual "restore" process of this file (reading it in, and merging the data, etc)

However, there are cases prior to this where the intent is to restore the file, but other pre-condition fail much earlier on, and the script was not called.

New cases to be included in the call of

1 - Invalid or missing tar.gz filename path

2 - Username cannot be parsed from the filename.

3 - Failure during creation of the account before the restore start

4 - creator conflicts, Reseller bob already manages User fred, but Reseller george is trying to restore it

5 - bad usertype settings from the live user.conf

6 - memory allocation errors when creating a new User class instance

7 - read errors of the live User account prior to restore

Some of these cases will honestly be highly unlikely to happen, but they're included anyway.

Checks 1-4 will use "unknown" for some of the fields, depending on how much info it already has.

These fields might be:

  • username

  • usertype

  • reseller

All others should be filled, as they would be known regardless of the failure.


Restore: Custom Domain Items fixed

Restore to include any custom domain item settings saved in the User's


nginx_proxy missing www->domain or domain->www redirect when force-ssl enabled fixed




ends up with only:

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

without force_ssl, it gives the proper:

        if ($host ={
                return 301$request_uri;

But with the first assumption, both should be present.

/var/log/user_logs/user/ to ensure logs are 644 (webalizer/awstats) fixed

The secured way that DirectAdmin lets awstats/webalizer run as the User, while being able to read the logs from the locked-down /var/log/httpd/domains area is by using hard-links.

The issue is that some boxes either set a strict umask or apache setting to save the /var/log/httpd/domains/ as root:root 600.

The issue arises when the hard-link is created, although the /var/log/user_logs/user is chmod 750 root:user, the root:root 600 log is not enough for the User to user read it.

Fix: added a mode check on a+r, if it's not set, DA will set it to 644.


lock_forwarder.lock: not removed fixed

Related to the new user limit check locking:

User limit check locking

Bug where after the modification of a forwarder, the lock_forwarder.lock was not removed, causing:

Unable to lock forwarder area: file is locked by another process

Fixed by clearing the lock after modification of the forwarder.


  • manually delete /usr/local/directadmin/data/users/username/lock_forwarder.lock

  • or wait 60 seconds for the lock to expire.

CMD_API_EMAIL_VACATION with json=yes generating blank output fixed

The CMD_API calls were not originally intended to support json, they were for URL encoded output.

However, the desire to want json output is entirely founded, so any cases where json isn't working with the CMD_API calls can be reported and will be fixed.

In this case, all CMD_API_EMAIL_* related calls call the same functions as the CMD_EMAIL style calls, however they have their own container to be passed back up to the CMD_API handler.

This container override thus trumped the JSON container, thus filling the API container, rather than the JSON container, causing empty json output when json=yes is added.

Specific call:


This is a blanket fix for all CMD_API_EMAIL calls* where the API container override will now be ignored in favor of the JSON container when json=yes is enabled.

Risk of affecting non-json CMD_API_EMAIL* call is present, however without json=yes, they should logically not be affected.

Confirm the URL encoding still works fine for CMD_API_EMAIL_VACATION?


ConfigFile class: read from string was ignoring last line if no newline fixed

Related to a similar fix for config files, where a missing newline character at the end of the file would not read that line:

readLine no longer ignores lines without trailing newlines

the similar read on a string, instead of a file, had the same behavior.

Fixed to notice there was buffered data before hitting EOL, and counting it as legitimate before returning.

JSON for CMD_API: backup send fixed

By default, CMD_API_* calls were not designed for JSON deliveries.

CMD_* calls (non-API) were setup for this, where action functions (eg: create an email account) have 2 flags:

  • api

  • json_out

where api==1 for CMD_API calls and json_out=1 for &json=yes calls.

As it wasn't account for in the design (CMD_API calls were coded years before we supported JSON with CMD_* calls),

there are logical scenarios where combining CMD_API calls with json=yes would not give json output.

This code change is a fall-back, where the unified CMD_API handler function now has a check for json=yes, such that JSON output will be sent,

if CMD_API and json=yes are both set.

As this is a single-level array, there might be cases where a JSON value has

"name" : "some=encoded&value=url"

in the even it's a double-encoded URL, so please let us know if you hit any and would like it resolved to be:

"name": {
   "some" : "encoded",
   "value" : "url"

as extra checks would be needed. Not yet sure if this will even happen.

March 18th, 2020:

Also reworked the header vs body "has_sent" flags, to prevent a 2nd call from trying to send data again.

This was caused by some functions sending their own data, while CMD_API calls send from outside the function, after it's exited.

Optimization on cleaning of brute_log_entries.list fixed

The file:


is used to track each attack on a service, by logging the time it happened, the log entrie, which IP did it, etc..

This file can get large, hence the importance of regular cleaning (Admin Settings: clear_brute_log_entry_time)

The previous cleaning method would load this file into a ConfigFile container, step through each item, and write any entries with a newer timestamp to a temp file, line-by-line.

After the write is done, the new file is renamed over the old one.

This new optimization does something similar as to the old line-by-line write, in that it now does a line-by-line read before deciding what to do with each line.

This prevents the need to save the entire file in memory, which for larger files, this can grow very large, causing an out-of-memory situation in some cases, causing the dataskq process to quit,

and leaving the uncleaned brute_log_entries.list file in place, allowed it to continue to grow, amplifying the issue on the next run.

Reuse Client IP buffer fixed

Bug recently introduced where the client's IP is returning a deleted buffer.

Fix: if buffer is already set, don't delete/re-create, but rather re-use the existing buffer as a client's IP for a given connection will never change.


Linux 64-bit static binaries: dynamic named.conf /var/named path (TEMPLATE) fixed

New internal defaults for the namedconfig and nameddir variables.

  1. The default data/templates/directadmin.conf will no longer have these vaules (All OSs)

  2. the values in the conf/directadmin.conf have priority over everything at startup.

  3. Static bins will have internal default to NULL, meaning OS checks are done at startup to determine the path to be used

  4. non-static bins will default to their respective OS


OpenLiteSpeed: LetsEncrypt on owned IP non-Vh items: eg: (TEMPLATES) fixed

With an owned IP, there is no global VirtualHost(VH) for in the OLS ips.conf file.

Some A records, like "" also do not have a VirtualHost.

The result, is there is no VH at all for, and no include of the httpd-aliases.conf file.

This is where things like the LetsEncrypt /.well-known/challenge and /roundcube aliases live.

Solutiuno: Added * to the main domain of an owned IP, eg:


vhAliases *

so that is caught on this IP in this master alias.

The vhAliases is ONLY set for the default domain on a given owned IP, for both 80/443.. but not for any subdomains.

Other domain on the same IP under this User will not get the vhAliases * setting.



Update to openlitespeed_vhost.conf


  vhAliases                 www.|SDOMAIN||SERVER_ALIASES|


  vhAliases                 |WILDCARD_VHALIASES|
  vhAliases                 www.|SDOMAIN||SERVER_ALIASES|

where DA only adds


when the case is needed.

The WILDCARD_VHALIASES token never exists if the above case is not true, eg:

  • owned IP

  • default/main domain

  • not a subdomain

  • is for both 80/443


Restore to box with dnssec=0 should not sign the zone fixed

A domain backed up with dnssec data, being restored to a box with dnssec=0 was signing the zone, simply due to the fact that the dnssec keys were present.

A box with dnssec=0 should never sign a zone, even if the zone was previously signed on another box.

However, the keys will still be restored, but simply not signed.

Also changed the deletion of a dns zone to always delete the 4 keys and files, else they would not have been deleted on a dnssec=0 box

(the .signed file shouldn't exist, but could if you did have dnssec=1, and shut it off later)

scripts/ also set directadmin.conf fixed

Previously, when changing the hostname from the Admin Settings, DA would run the script, then DA would set the directadmin.conf.

This is fine unless you're only trying to change the hostname from the script, but also want the directadmin.conf to be updated.

This is a change to the scripts/ script, such that it will always run:

./directadmin set servername

and issue a task.queue restart of directadmin, even if the Admin Settings calls it.

This allows the hostname to be set everywhere, regardless of how it's triggered, without needing 2 calls if done from ssh.

Now, when done from Admin Settings, the directadmin.conf will be set twice, no harm there, and because both DA and the use a task.queue restart,

the duplicate restart will be filtered out by the dataskq, so directadmin only gets restarted once.


Nginx: Subdomains :redirect to/from / fixed

The "Force Redirect" domain option to redirect to or vice versa, was applying it's settings to the Nginx subdomain server{} entry, but was checking:

if ($host ={

instead of:

if ($host ={

Fixed to set proper subdomain host check.

Crons: crontab returned non zero value: chown: Operation not permitted fixed

Confirm on Debian 8, the call to write the crontab was not running as root when setting a new cronjob.


One-Time Login Hash URL: allow login_keys_notify_on_creation=0 override fixed

When creating a Login Hash URL from CMD_LOGIN_KEYS or CMD_API_LOGIN_KEYS, the POST option:


was not being respected.

When set to 0, no notice will be sent to the User at hash creation time, nor login time when the hash is used.

If set to 1, a notice will be sent at hash creation time and again when the hash is used.

If your directadmin.conf (or internally) has:


Then absence of the POST value will use the directadmin.conf value.

If the directadmin.conf has login_keys_notify_on_creation=2, then notices will always be given, the POST may not override it.

If the directadmin.conf has login_keys_notify_on_creation=0, then no notices will ever be given. the POST may not override it.

Creating the Login Hash URL from ssh using --create-login-url, no notices will ever be given when the hash is created.

A notice will be given when the hash is used (unless directadmin.conf has login_keys_notify_on_creation=0)


CMD_JSON_LANG to optionally touch session file to extend session time, and new: upload_idle_timeout=120 fixed

If you're uploading a very large file through the FileManager, the main upload request does not bump the session expiry time.

The skin could end up logging out prior to finishing the upload.

During the upload, the CMD_JSON_LANG is triggered often, so code has been added to optionally touch the atime on the session file with the utime() function to extend the session time from this point, if the touch_session=yes is passed in the request.

So to maintain the login session during a file upload, change the GET call to CMD_JSON_LANG to include the touch_session=yes variable.

Once the upload is done, return the CMD_JSON_LANG to not include this variable.


Also, new internal variable for multi-part/form-data uploads:



  • chunk size has been bumped from 2048 to 16384 bytes, for both socket reads, and buffer read/writes during parsing.

  • Every 10 read cycles, the alarm is set back to upload_idle_timeout, so a timeout for large files is far less likely, as it continuously resets.

  • there is a sliding max amount of time, based on the content-length, to a certain limit.

  • after the upload is done, the alarm is again reset to the large value to allow sufficient time for the POST data to be split into it's respective files.



Alternatively, ANY CMD call which passes header:

X-DirectAdmin-Touch-Session: yes

will also have the session file touched.


Access various /etc/virtual/ files while domain is suspended fixed

When a domain is suspended, the folder is renamed to:


these changes automatically do checks so the various files can be read from domain.com_off automatically, if is suspended.

Mainly aimed at passwd, passwd.cache, quota, aliases files.

Swap unknown `TOKENS` with |TOKENS| for future processing fixed

Due to the order tokens are loaded (with the template being read in and processed last, after all internal and custom tokens being loaded first),

if you were to set some:


where TOKEN has not yet been set, the call to |TOK| would end up filling `TOKEN` in the output even if TOKEN was set before the call to |TOK|, but before TOK was set.

The solution is for DA to take note of all unknown tokens (it already had this list), and for unknown values where they're set to `TOKEN`, swap that TOK value to be |TOKEN|.

When the |TOK| is called later on, it will fill |TOKEN| which can then be tokenized into whatever value TOKEN should have had set in the first place.

This currently applies to the apache/nginx/litespeed/openlitespeed custom templates.

dovecot_proxy: action=rewrite&value=email_passwd was not setting the caller IP in email passwd fixed

Related to dovecot_proxy=1 feature:

but combined with email sync feature where DA's multi-server setup can keep both ends synced:

Cluster: Remote E-Mail Account sync

The issue was with master box A, issuing a local rewrite, eg:

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

where the slave box B was not setting the master box A's host=A-IP in the /etc/virtual/ file.

It was incorrectly setting host=B-IP.

This fix tracked down the missing proxy_ip=connection flag, being passed to B, thus having B correctly use the client IP in the host (A's IP).

During the debug process, a similar bug was also found in the "modify" code, where changing a password or quota from GUI/API on A would also result in the wrong B-IP in B's passwd file.

Enforce vacation start time to be before end time fixed

You were previously able to set a vacation message's start time ahead of the end time.

This wold prevent the vacation message from ever activating.

This change ensures that the start is always before the end, to avoid confusion.

To disable a vacation message, simply set the end time to the past, (or delete the whole message)

The start time would still need to be before this time.


swap da_sso definers for PROCEDURES/FUNCTIONS fixed

If you use the one-click single sign-on (SSO) feature for phpMyAdmin, the username used for the login has a prefix of:


If an import of a routine or function is done through PMA using this account, it will set the "definer" as this da_sso_* account name.

This is only a temporary account, and DA deletes it after it expires, thus leaving the functions/procedures without a valid definer, causing errors such as:

Connection failed: SQLSTATE[HY000]: General error: 1449 The user specified as a definer ('da_sso_1234ASDF'@'localhost') does not exist



The solution is that when DA runs through the list of da_sso accounts to clear out, if ANY function or procedure in the mysql.proc table has a definer LIKE da\sso\%@%, DA will use the "db" column, eg:


to obtain the actual owner, and swap the definer to be username@<host>, where DA also maintains the host value from the old definer.

This check is done anytime the list is saved.. including adding a new value, or with the nightly cron to clear them.


Domain Pointers & named_rndc_addzone=1: write order swap fixed

When the instant dns reload is enabled, eg:



The zone db must exist first. Domains were fine, but pointers needed to be swapped (db zone first, then addzone call).


Relating to the mail_partition:

Custom partition location for email

in combination with:


which is enabled by default now...

The login for system account "fred" will use /etc/passwd

The login to the same will use /etc/virtual/

The /etc/passwd files does not have it's directory changed with mail_parition is changed, thus the different logins end up in different folders.

The fix sets a symlink from /home/fred/Maildir -> /mail/fred/Maildir


Remove additional User IP to ensure more than zero zone IPs fixed

Say you have User with IPs:

The domain only has the additional IP:

When removing from the User (with dns removed too), DA would remove it from the zone and if it ended up with zero IPs, it would re-add the default IP again.

This check was fine for apache, but as the only remaining records were removed prior to the new IP being added, there was no record data to duplicate, so the default didn't add any A records, etc.

Fix: change order of check to add any needed IP prior to removing the last IPs from the domain. This will ensure A/AAAA values (etc.) are duplicated prior to removing the only remaining IP, thus not an empty zone.

Database: do not allow domain access hosts if skip_name_resolve=ON fixed

For MySQL and MariaDB, if you disble dns lookups with the skip_name_resolve=ON option in the my.cnf, the means lookups are not done on hosts.

This also means that any login IP checks will fail if the value entered needs to be resolved (rDNS).

As a result, when you try to add an access host in DA, if you have skip_name_resolve=ON, DA will block any or wildcard values from being added with message:

access hosts cannot be domains because skip_name_resolve=ON

That is not a valid access host

Without this check, MySQL/MariaDB gets into a broken state where the mysql.user table/view DOES have the values,

but any GRANT calls to also add it to the mysql.db table fail, resulting in a broken state.. not visible in DA for removal, and cannot add them.

Also causes errors during password changes since DA tries to update all access hosts in mysql.db but they don't exist.


Restore from system_user_to_virtual_passwd=0 to box with =1 not adding system account to email passwd fixed

DirectAdmin was not adding the system account to /etc/virtual/ when system_user_to_virtual_passwd=1 is enabled on the restore box,

when the system account was not in the same file on the backup server.

Extra check added to ensure it exists, and add it if missing using same system account call as task.queue action=rewrite&value=email_passwd does (surgically for this one account, not a full rewrite/check)

Password crypt taken from /etc/shadow.

When this check and add is triggered, you'll get a note in the errortaskq.log:

Email::restoreEmail: system account %s did not exist in the %s passwd file. Adding it to restored file.

where %s is replaced with the system account name, and the 2nd %s is replaced with the domain being restored.


Office365 MX template: extra spaces fixed

The 2 records in data/templates/mx/office365.txt:

lyncdiscover 3600 IN CNAME

_sip._tls 3600 IN SRV 1 100 443

had extra spaces cause DNS syntax issues.

Note, the wording for 1.60.5 will rename this template string to "Microsoft 365"

The template filename and selct-box value will remain as office365.txt / office365.

Upload files: Use mkstemp instead of tmpfile for location control fixed

The upload of files using multipart/form-data was using tmpfile() to create a file location for upload.

This usually ended up in /tmp.

For very large files, this might not have been large enough.

Changed to use mkstemp, so we can use the tmpdir=/home/tmp directadmin.conf variable, thus using /home/tmp (or any desired path, via tmpdir var), so you can control where this larger data might end up.

Note the tmpdir is used for various other things, not just uploads.

Compile time: May 6 2020 at 16:41:18

FileManager: Trash restore: restore correct parent folder permissions fixed

When restoring a file or folder from the /.trash folder, or similarly when creating a parent /.trash/* path to move a deleted file/folder into, ensure that if a new parent path matches the old one.

This is to ensure that if DA is creating any paths upon restore, the created parent values correctly match what was previously there.


Clear stale users.list from memory to prevent race condition fixed

Relating to the change here:

Race condition during account deletion

added missing item to clear the live memory data prior to the re-read, prior to the write.

Without the clear, the memory would be stale.


biguser takes 1 minute to delete.

smalluser triggerd 2s after biguser, takes 5s to delete.

smaller user is done being deleted first, the users.list only has biguser left.

The process deleting biguser still has smalluser in it's list, thus smalluser ended up being re-added to the users.list.

There was a re-read of the users.list to catch new accounts, but it didn't clear deleted accounts, thus the full wipe was needed first.


Compile time: May 18 2020 at 16:05:05

Last Updated: