Version 1.59.0

Released: 2019-09-22

SNI for https client domains on port 2222 new

Feature to add SNI support into DirectAdmin (not referring to apache's https connections on 443 which is already supported)

This refers to connections made to 2222 where, once working, any domain which has a valid certificate on the server should be able to connect to:

even if it's not setup in the cert setting for 2222 in the directadmin.conf.

Current, the only workaround is to create a multi-domain cert on the hostname:

which DA will then use, but this is not SNI, as all listed domains exist within this 1 certificate.

You would need to disable the:


setting from the directadmin.conf and restart directadmin.

The ssl_redirect_host='s behavior is now slightly different,

in that if http is used to connect to the https in DA, the host header is now able to be sorted out (even though openssl ate some of the request during the handshake)

where if a cert/key exists for the, the redirect will go to

However, if there is not a cert, it will go to the ssl_redirect_host= value.

Compile time: Sept 10 ~15:30+

This feature makes use of the file:


which is filled when:


is enabled.

It's automatically updated when new certs are added or changed.

Custom Package Items values available in virtual_host2.conf templates new

Similar to Custom Domain Items tokens for the templates:

Custom Domain Items values available in virtual_host2.conf templates

this feature will add Custom Package Items into the templates.

Token will be |PACKAGE| which might be the package name, 'custom' or possibly blank in rare cases.


User limit for Resellers (PACKAGES)(SKINS)(LANG) new

New fields, which will default to unlimited, to let the Admin set a maximum number of Users that a Reseller is allowed to create.

The variable name will be nusers, and will show up in the reseller.conf, reseller.usage, as well as packages.

Skins will also need be updated to account for this, but if not, DA will just default to unlimited if they're not set.


Using the package method will not change.

But the creating with the custom method:


will include either:

nusers=0+   #for the max number of Users this Reseller can create. Can be set to nusers=unlmiited too, but match any current style you're currently using (eg: unusers=ON)


unusers=ON     #to set this Reseller to create unlimited Users.

absence of both will set the reseller.conf to nusers=unlimited



Similar to the custom Reseller creation, the users and unusers will be part of the packages.

Absence of both will result in the package having nusers=unlimited.

Tokens in admin/show_reseller_package.html





same tokens/variables as other places.


|RESELLERUSERSMAX| : taken directly from the reseller.conf.






Added "nusers" and "unusers" Unlimited checkbox.






One-Click login to any phpMyAdmin database from DirectAdmin (SKINS) new


New feature, off by default using internal value:


which can be enabled with:



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

allows for a one-click login to any database when viewing that database through DirectAdmin, eg:


Note: it's on the database page with User list, not on the database list page.


To enable, turn it on in the directadmin.conf and also re-install PMA:

cd /usr/local/directadmin/
./directadmin set one_click_pma_login 1
service directadmin restart
cd custombuild
./build update
./build phpmyadmin
  1. The database page has a form, which will submit to:
Method: POST
name=user_dbname    #required, but only for return reference
  1. This action will create a new da_sso_RANDOM user to access the user_dbname db.

It's added into MySQL, and DA also logs the creation in:


so it can track creation times, and clean up the accounts later.

  1. A token is created in:


with the user,pass,client IP and creation time.

  1. An auto-submitting form goes to:


which will read in this token and setup the session using phpMyAdmin's auth_type=signon method.

  1. The direct_login/index.php then redirects to /phpMyAdmin/index.php for normal use.

  2. The logout will redirect to /phpMyAdmin/direct_login/logout.php,

which destroys all SignonSession cookies, so a normal user/pass login can happen if /phpMyAdmin is accessed manually.


Because we only want to use one $cfg['Servers'] in the PMA, we're currently using an if-then-else method, checking for the presence of $_COOKIE['SignonSession'].

If set, it uses the $cfg['Servers'][$i]['auth_type'] = 'signon';

If not, the standard cookie method is used instead.


The da_sso_* MySQL users are added to a given User's database.

As a result, they would normally show up in the count and in the list of DB Users through DA...

But DA goes out of it's way to hide them from counts and listings, even though the are there.

The /usr/local/directadmin/data/admin/phpmyadmin_sso.conf lists all allow users.

The nightly tally will clear any accounts when they expire after 16 hours.

Any da_sso_* user found in mysql that is NOT in the phpmyadmin_sso.conf will be removed for the database.

Any account in phpmyadmin_sso.conf that has a creation time greater than 16 hours, will be removed from both phpmyadmin_sso.conf and the database.



add a new form:

<form id='pma_form' action='CMD_PMA_LOGIN' method='POST'>
<input type='hidden' name='name' value='|name|'>
<input type='hidden' name='domain' value='|DOMAIN|'>
<input type="submit" value="phpMyAdmin SSO"> Login, no password required.

Ability to set maximum value for email quotas new

Related thread;

Users can set any value they want for email account quotas.

This does not mean the email usage can exceed the User's own quotas.

New feature, for now just a directadmin.conf (possibly user.conf / package override later) where the internal default is:


which means Users can set any quota they want (0 == unlimited)

But the feature will allow other values:


where -1 will float the limit to the User's own quota limit.

And anything above 0 would be in bytes, so setting:


would limit the User to setting a max of 1 meg per email account.

You can also set an override in any user.conf file, eg:


which overrides the global value.

This will make it easy to set this with any custom package item, so the limit can be determined via package settings.

Ability to add your own package items (SKINS)


RC2: Compile time Sept 11, ~23:45


Dynamically determine MySQL/MariaDB versions for query syntax new

DirectAdmin currently relies on directadmin.conf settings, like:


to determine how the syntax for creating/droping databases, users/passwords, etc.. are all done.

New directadmin.conf setting for now disabled:


where you can enable it by running:

cd /usr/local/directadmin
./directadmin set mysql_detect_correct_methods 1
service directadmin restart

Which will make use of:

This change will use:


right after the initial connection by DA, to figure out the version of MySQL, eg:


or MariaDB:


and with that DA, can dynamically set the above 2 variables on the fly.

This saves the need to make a raw /usr/sbin/mysqld -V call to sort that out, which is slower, and doesn't work for external DB servers.


MySQL 5.1, 5.5, 5.6:

else (MySQL 5.7 8.0)

MySQL 8.0


MariaDB *

10.2, 10.3, 10.4

Where there are some internal exceptions, even after being set.

MariaDB 10.3 and lower does support ALTER USER LOCK|UNLOCK, so the LOCK/UNLOCK will use the old UPDATE mysql.user to set !passwordcrypt


Command-line: DirectAdmin can give you the expanded ipv6 new

New option that lets you call the directadmin binary to get an expanded format of an ipv6, eg:

[root@server directadmin\]# ./directadmin --expand-ipv6 ip=::1

Can be json if needed:

[root@server directadmin\]# ./directadmin --expand-ipv6 ip=::1 json=yes
        "ip": "0:0:0:0:0:0:0:1",
        "success": "1"

Errors will be shown like this:

[root@server directadmin\]# ./directadmin --expand-ipv6 ip=foo
error=ip=foo is not an ipv6 ip
[root@server directadmin\]# ./directadmin --expand-ipv6 ip=foo json=yes
        "error": "ip=foo is not an ipv6 ip"

if a full format was returned, directadmin will exit with code 0

If there was any error, you'll get exit code 12

scripts/ftp_upload.php to use curl for FTP new

The old ftp_upload.php used ncftpput for FTP and curl for FTPS.

As curl is more modern, we've swapped out the whole script with curl for both FTP and FTPS.

Stored in:


pigz can be managed by system. new

Running ./build pigz will install pigz to:


but some prefer to manage pigz/unpigz with the system's package manager, eg:

yum install pigz

This change will let you enable it in the directadmin.conf:

./directadmin set pigz 4

just as before (where 4 is the number of threads you want.. usually as high as the number of cores you have)

But if /usr/local/bin/pigz is missing, DA will hunt for:


and automatically internally store them into the pigz_bin and unpigz_bin settings.

If your pigz and unpigz binaries live somewhere else you can tell DA to use that instead:

./directadmin set pigz_bin /some/other/path/pigz
./directadmin set unpigz_bin /some/other/path/unpigz
service directadmin restart

Ability to select Users and set package in mass new

Related to:

Reseller Level -> List Users

in Enhanced, it's CMD_USER_SHOW.

The form will now offer the ability to select the desired Uses, select the desired package, and click "Set".

This saves the need to manually set the package for each User, one at a time.


The command to save will be:

method: POST

The CMD_USER_SHOW?json=yes request will offer extra values:

                "selected": "yes",
                "text": "-- Select Package",
                "value": ""
                "text": "unlimited",
                "value": "unlimited"



Immediately unsuspend on limit increase over suspend limit new

If a User becomes automatically suspended by the tally (say for Bandwidth overusage),

the creator may decide to allocate them more bandwidth, either with:

  • Temporary Banwidth Limit increase

  • upgrade to a higher package

  • Manually setting a higher bandwidth limit for the User

if any of these end up pushing the User back below their suspension limit, then they'll be immediately unsuspended.

Only relates to viewing the that specific User with either:


Where saving the limit increase should only be from the one of these 2 pages.

Setting a package to the User from any other area, like saving a change a package, or the mass-user package assignment, will not auto-unsuspend at this time.


End-Of-Life: Debian 7 LTS, FreeBSD 8 new

Debian 7 LTS has it's end-of-life in May 2018.

DirectAdmin's official end-of-life for Debian 7 was May 2019.

FreeBSD 8, August 2015

DirectAdmin's officail end-of-life for FreebSD 8 was August 2016

This release of DirectAdmin will not include Debian 7 nor FreeBSD 8.

If you are using these operating systems, please consider upgrading to something more current.

Ability to hide memory in System Information: ram_in_system_info=1 new

New directadmin.conf option, internal default:


where you can set it to 0, and this will hide all memory info from the System Information page (CMD_SYSTEM_INFO)


Ability to disable a skin or skin language new

If you wish to hide a skin or a language, you can now create a .disabled file in the relevant directory, eg:

Disable/Hide a skin:

touch /usr/local/directadmin/data/skins/enhanced/.disabled

Disable/Hide a language:

touch /usr/local/directadmin/data/skins/evolution/lang/en/.disabled

where the presence of the .disabled file will skip it from being added to the list.

Compile time: Sept 10, ~16:15+

Option to disable User access to per-email smtp logs new

Relating to newly added feature:

User Level Exim log info per-account, per-ID (SKINS)(EXIM_PL)

This adds the option to disable it.

New internal default, enabled by default;


To disable the "SMTP Logs" feature for Users, set:

./directadmin set user_email_smtp_logs 0

which would add user_email_smtp_logs=0 to the directadmin.conf,

then restart directadmin.

Compile time Sept. 10, ~22:30+


CMD_API_DOMAIN_POINTER: full output (local email) new

Optional GET value when requesting:


where full=yes changes the per-pointer output to be URL encoded list of:


where without full=yes, you'd just get:


as the pointer value.

Compile time

Sept 11, ~22:30

zip=1 by default for new installs new

We've added zip to the pre-install commands, thus allowing us to enable zip=1 by default for new installs.

Existing installs will be unchanged, but can be enabled with:

yum install zip unzip
cd /usr/local/directadmin
./directadmin set zip 1
service directadmin restart

or Debian:

apt-get install zip
cd /usr/local/directadmin
./directadmin set zip 1
service directadmin restart

Create one-time Login URL via GUI:2222 (SKINS) new

Relating to the root ssh command-line method to create a One-Time Login URL:

One-Time Login Hash URL

This feature simply lets you create the one-time URL, but though the 2222 DirectAdmin interface.




Added 2 radioboxes:


where key is the default.

If you select the type=one_time_url radiobox, the keyname, key/key2, and expiry inputs are hidden, as they're not needed.

It also sets:


The output is a standard dynamic method, where error=0 will include the url in the details field.

Compile time: Sept 18, 2019, 20:35+

method: POST
key=secret                 #the new secret password. Usually something long, say 128 chars.
key2=secret               #re-poost key
passwd=currentpass    #password of the current account.  If using the login-as method, then it's the password used for the login-as (eg: admin's password)


expiry=3d                   #units:  s, m, h, d, M, y  (m for minutes, M for Month), no units are seconds.   Optional: if not passed, defaults to 3d
redirect_url=/CMD_PLUGINS/softaculous/something.html     #optional: lets you redirect immediately after login.
ips=,  #optional, don't pass, any IP is allowed

Using "allow" blocks all commands not in that list.

deny only blocks those commands.

Using both allow and deny has no common purpose.

See the Login Keys for more details on the values:

Login key to login with a different, more restricted password (SKINS)

If set_php_bin_path_in_crons=1 show "php" instead of /usr/local/bin/php for cron example (SKINS) new

Relating to the ability for crons to have "php" set to the correct version via the php version selector:

Add the php binary path to cron PATH

this is a cosmetic change to change the path to php binary on the cronjobs page.

The sample cron for php by default specifies:


but with this change, if you are using:


Then the php binary will be just:




|PHP_BIN| /home/|USERNAME|/domains/<br>




will now also include a variable at the top level:

"set_php_bin_path_in_crons": "0"

which can be set to;

0 :  show /usr/local/bin/php
1:  show php

MX Templates: also affect domain pointers (SKINS) new

Relating to the MX Templates feature:

MX record templates (SKINS)(TEMPLATES)

This change has a checkbox, checked by default in the skin:

[x] Affect Pointers

where when saving a given template to a domain on the "MX Records" page, when checked, saving that template will add that template to domain pointers.




<input type='checkbox' name='affect_pointers' value='yes' checked>


Compile time: Sept 19, 2019, 14:30+

Brute Force Monitor: xmlrpc.php POST 200: add 8x multiplier (TEMPLATE) new

The xmlrpc.php is a file included with WordPress is an API file, used for data transfers or actions between various things.

A stand-alone setup often does not need web access to this file, but any external connections, like iPhone apps that routinely do POSTs to it generating a 200 return code would require it.

This is also one of the major attack points for WordPress, in attempts to determine the website's WordPress password.

As a result, our Brute Force Monitor (BFM), when "Scan for WordPress attacks" is enabled in the Admin Settings, will scan for POSTs to this file, with a valid 200 return code. (wordpress2 brute_filter.list).

This change is to increase the match count, which would have been the same as the Brute Force Monitor's count limit (set in Admin Settings),

to now be 8x that value, to allow for possible valid usage, while still blocking attacks.

For example, if you set your ip_brutecount=100, aka:

"Notify Admins after an IP has: [100] login failures on any account"

This would mean that any User would be allowed 800 POST with a 200 response code to the xmlrpc.php file, based on the BFMs rules.

Note that the BFMs time window is sliding and is entirely based on a period of time where there are no attacks counted in order to reset the count to 0.

So if you have, Admin Settings:

"Reset count of IP/User failed attempts: [4] hours after last attempt"

a period of 4 hours must pass where there are 0 failed attempts from this IP address, in order for the count up to 800 to reset to 0.

If even 1 POST to xmlrpc+200 is done per hour, then the count will never reset, and the 800 limit WILL eventually be hit.

We'll continue to evaluate other options for this.

There are plugins for WordPress, which can monitor for such attacks, saving the need for DA to do it, such as WordFence.

It's ultimately up to a website to be protecting itself, as the "Scan for WordPress attacks" is simply there to be a fall-back in case the website owner failed to secure WordPress.





wordpress2=ip_after=&ip_until= -&text=] "POST /&text2=/xmlrpc.php&text3=" 200%20

to be:

wordpress2=ip_after=&ip_until= -&text=] "POST /&text2=/xmlrpc.php&text3=" 200%20&count_multiplier=8

Subdomain owner check for remote dns servers without Users fixed

Related to:

Check remote subdomain owner

solve the subdomain owner check on a master server, if there is no User linked to the zone.

Note, this is disabled by default.

Once it's working and everything is synced, it's recommended to increase the strict level to 2, which no longer trusts the clients, and ensures all data pass is valid, verified and correct.

At the basic 1 level, it still blocks, but only based on a trusted level of the hostname being passed, and only if the values exist in the cluster_domainowners file at the time of the check.

You really only need this feature if you have multiple DA boxes pushing to a shared master.


Say you have a master nameserver (we'll assume domains only use 1 NS for the example, for simplicity)

The master ns1 is remote box ns1.

You also have hosting and hosting, both of which push their domains to ns1.

User on box1 adds

ns1 gets zone, but has no associated User with it, it's a zone-only transfer.

Some other User on box2 tries to add, this is blocked, since the zone exists. This is all good so far, block is fine.

However, if a User on box2 tries to add, this is allowed which is not correct. It should be blocked.

The ns1 should be blocking this, since it belongs to some other User.. but there isn't sufficient information on ns1 to figure this out.


When enabled it on ns1, a new file will be created on ns1 as zones are updated/saved remotely and pushed over (assuming they're using DA 1.59..0 +)


which is similar to the usual /etc/virtual/domainowners file, except it's in the format:

where the username may not always be set, depending on what the client sent over.

The main thing is that the hostname is visible so that it can be checked against the hostname proivded by the box1/box2 during their call:



The 2 hosting boxes box1/box2 do not ned


Internal default:


To enable this check, set the following on the master ns1 box:

./directadmin set check_subdomain_owner_in_cluster_domainowners 1

service directadmin restart

but once it's working and things are synced, set it to 2 (below)


If you set this on ns1 to be:


then all sending DA boxes (box1/box2) MUST provide the hostname in the GET portion of the action=rawsave zone transfer.

Without it, the master will refuse to save the zone.

Basically, update ALL of your DA boxes at the same time.

When 2 set DA will also do a lookup on the hostname being passesd.

The ns1 box will do the lookup and it must resolve to the IP that is connecting to ns1.

AKA: must resolve to the IP that connects to ns1.

Setting this back down to 1 is useful if you're moving servers, and the hostname will be changing to some other IP, but you still want things to be saved, for when the check if more strict later on.

Compile time: Sept 11, ~21:25


DirectAdmin System E-mail: Only encode subject/to/from/reply-to if they're 8-bit fixed

Relating to:



When they're set to 1, they will now only base64 encode the subject/from/to if those given values actually contain 8-bit characters.

Encoding 7-bit-only strings can actually be detrimental to spam scoring, eg:

REPLYTO_EXCESS_BASE64 Reply-To that contains encoded characters while base 64 is not needed as all symbols are 7bit (1.5)

FROM_EXCESS_BASE64 From that contains encoded characters while base 64 is not needed as all symbols are 7bit (1.5)

So the 1 settings for both of these commands will now only encode when needed (8-bit chars).

If you want to force base64 bit encoding all the time, just set these values to 2, eg:



To clarify, an 8-bit character is any with a ascii decimal number higher than 255.

systemd: wait until after to call startips fixed

For systemd installs of CentOS and Debian, the script at:


was previously waiting for:

Although the had actuall started, in some cases that does not mean it was done loading yet.

Change it to be:

stored in:


if you wish to copy it over for existing installs. eg:

cp -f /usr/local/directadmin/scripts/startips.service /etc/systemd/system/startips.service
systemctl daemon-reload

This will only affect new installs, unless you manually copy it over.


Delete old MySQL test data fixed

It's been reported that some DirectAdmin boxes may still have the default "test" database.

To confirm it's all cleaned up, this update will check and remove any such databases, if empty.

If they're not empty, you'll be notified with the Message System on how to delete them, eg:

echo 'action=delete&value=test_dbs&all=yes' >> /usr/local/directadmin/data/task.queue

If you do not get any notice, there is no data in them or they don't exist and will be cleaned accordingly.


Clear old RoundCube data on User deletion fixed

Clear old email account from da_roundcube database upon User deletion.

If a domain has PHP disabled, override with these tokens:






Also add the following to User httpd.conf files to help avoid php confusion:



|*if PHP="OFF"|
#PHP is OFF for this domain

So it will explicitly say that php is disabled.

We've had several tickets about this, so this should help the confusion.

When a directory is being protected, the User opernlitespeed.conf files were not being loaded with a public_html/protected directory as the path assembly noticed the public_html was not in the private_html path.

Extra check for the private_html -> public_html symlink to override this check.

compress_rotated_logs=1 did not rotate old months logs fixed

When using compress_rotated_logs=1 for your:


the new algorithm to determine which logs to delete was broken as it was only looking at the .1, .2., .3, .4 files.

The issue was that old months, eg Aug:











were being left behind as a direct "Sep" match wasn't even looking for Aug.

Reverted the algorithm to simply match the relevant file timestamp, instead of filenames.


Rewrite clearing of access hosts for system account during DB removal fixed

DA has use an embedded select query to clear access hosts for the system account.

For systems with tens of thousands of Users, this can be slow due to the way it's written.

Solution will be to sort out which entries to remove from mysql.user via other means (per-Db host requests),

and to also use the proper DROP USER requests for those access hosts, so galera/remote clustering gets properly notify of these removals.


Removal of access hosts clearing from mysql.user for shared user fixed

If you had a shared User spanning multiple databases,

then removing an access that was on both databases ended up clearing the user@ from mysql.user,

preventing user@ from being used on the other DB.

The old method was simply clearing all instances from mysql.user and mysql.db, which was not using the new REVOKE/DROP USER methods anyway.

This change makes use of the mysql_use_new_user_methods option (including mysql_detect_correct_methods=1), and will instead:

  1. Revoke user_dbuser@ from user_dbname.*

  2. If user_dbuser@ exists on no other database in mysql.db, DROP USER from mysql.user.

This fix also affects the system account, which is shared over all DBs, and was incorrectly being cleared from all DBs for that given access host, when the host was only removed from one DB.

This also changes the logic for older MySQL/MariaDB versions not using the new method (using mysql_use_new_user_methods=0, or mysql_detect_correct_methods=1 detects old version)

Database: Acecss Host: allow wildcard ipv6 with mask fixed

IPv6 IPs were allowed, but this fix is to allow this type of value:


Also allow IPv6 with masks:


Compile time: Sept 12, ~17:20


dkim=1 on restore with backup that did not have dkim does not setup dns fixed

The dkim=1 setting means that when a domain is created, DA will automatically setup the dkim keys, and dkim TXT records in the dns.

This works correctly.

During restore, if a user/domain is being created, this still happens normally, and the dns does get the TXT record, and dkim key files.

However, then the restore portion of the dns from the backup happens, it does not merge TXT records, since it's only loading the values from the backup, and the new dkim TXT record vanishes.

Fixed, such that during DNS restore, if all are true:

  • user is just created

  • dkim==1

  • current zone (just created) has the x._dominkey TXT record

  • backup to be restored does NOT have the x._domainkey TXT record

Then the live x._domainkey is transferred over to the restored value.

Compile time: Sept 13, 2019: ~18:55+


Repair Users+Resellers where they are their own creator fixed

Relating to the previous checks that were added to address cpmove issues where some accounts were their own creator:

Internal admin/reseller/users.lists checker

This check applies to all User class reads where it checks the user.conf for usertype!=admin (Resellers and User), where the creator=* matches their own username=*

If this happens, DA will find the first Admin in admin.list file, and issue the task.queue command for id=2441 to automatically repair things in the background.

Compile Time: Sept 19, 2019, 15:00+

add /opt/alt/PHP*.../pear/ and /dev/urandom to open_basedir path (TEMPLATES) fixed

The templates have been updated. Any token that was set to:


has been updated to include:














Compile time: Sept 20, 2019, 5:09pm+ to support /home2 fixed

The old used hard-coded /home paths.

The fix uses proper lookups of the /etc/passwd for both before and after the swap (in case usemod had other ideas of where the new home path should be)

Last Updated: