Version 1.60.0

Released: 2020-01-27





Show list of available logs to view.

This is basically just a multi-level URL encoded copy of the ?json=yes output

The result will give 2 main variables:

files=<url list>

where "lines" refers to the number of lines to grep, should that option be selected.

files would be a double-encoded list (decode the value, to get another list), where each index is a filename, and the value is an urlencoded list, currently containing "size=1234" for the size of that file.



Where you'll get the same output as above, except you'll also get:


containing the log.


In addition to the "file" in the GET request, you can also include:

lines=50   #meaning, it will be the tail -n 50 of that given log.
type=raw  #the raw log it output. No encoding.
grep=<str>  #where <str> is any match of a line you'd like to filter.
invert_grep=yes   #if grep has a string, it will NOT show the grep string, and will show all other lines
ignore_case=yes  #when passed, grep filtering matching any case



Default Change: realtime_quotas=2 new

New internal default, will apply to all existing installs and new installs:


This will make use of the system quotas to let Users see their usage in realtime.

If you need to disable it, set it to 0:

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

Does not apply to FreeBSD, which will remain as 0.


If you're noticing the quotas are returning a value, but does seem to be too low, ensure the correct quota type is set in the directadmin.conf.

Check your mount of / (or /home), eg:

[root@server directadmin]# mount | grep ' / '
/dev/mapper/vg_es664-lv_root on / type xfs (rw,relatime,attr2,inode64,usrquota,prjquota)

If that's showing xfs, then ensure you're using use_xfs_quota=1:

cd /usr/local/directadmin
./directadmin c | grep use_xfs_quota

if it shows 0, set it to 1:

./directadmin set use_xfs_quota 1
service directadmin restart

If the mount shows ext4, and you see use_xfs_quota=1, then set it to 0

Show Restore Tracking info in Admin Backup/Transfer new

New "restore" type of process tracking for restores, similar to the existing backup process tracker.

The Level 1 percent bar is based on the User-data only backup settings, meaning there will be 3 phases per user (if applicable):

1 - cpmove-to-DA conversion
2 - decryption
3 - user restore

Each phase will go from 0 to 100% (although the first 1 are just full steps)

The larger level 0 percent bar is as it is for backups, either 1 or 2 per User, depending on if there are ftp downloads involved.

So 10 Users will have 10% steps, unless downloading from ftp, then will be 5% steps.

The "Active Backups" table on CMD_ADMIN_BACKUP will have a new column after "Start Time" showing "Type".

Each row will either be type "Backup" or "Restore"

The "Backup ID" column will always be blank for restores, since restores don't have crons.


Although the older JS method of calling browser function still works for this, we've added support for proper SSE streaming.

To make use of the new streaming syntax, simply include type=sse in the stream request, eg:


The start of each stream will begin the line with "data: "

The next part will be {} json data, followed by 2 newline characters.

More/immediate data: lines could follow at any time, even in the same packet.

DA does assemble all sections into one data packet, when possible.

Possible data variables:

finished: 0|1   #always sent
percent: 0.00-100.00     #do not assume 100 means the stream is done. There are a few steps which will jump around.
dynamic_text: <some text>
dynamic_details: <some text>

#items relating to file compression/growing:

process_info: #not used, relates to add_process_info() JS function

quota: 1234 #bytes, disk space used by the User account, to give a rought max size of the compressing file.

quota_readable:" (Account Usage: 1.23 Meg)"

filesize: 1234 #size of the tar.gz as it compresses. Compare this to quota for your %

filesize_readable: 1.23 Meg


data: {
  "dynamic_text": "Restoring User from user.admin.backtest.tar.gz",
  "finished": "0"

data: {
  "dynamic_details": "Extracting backup_options.list",
  "finished": "0"

data: {
  "dynamic_details": "",
  "dynamic_text": "Backup Complete",
  "finished": "1",
  "percent": "100"

Note: The texts above will not be html encoded.

The json encoding will ensure quotes are properly escaped, but ensure you html encode any scripts before inserting them into your document.


If you have small restores but wish to see the progress and need to slow it down, use debug level 2019 in the dataskq.

Eg, issue the restore through the GUI and quickly run:

./dataskq d2019

where each tracking "step" adds a 5 second sleep, allowing time to debug what the output is.


The current_backups will have a new column called "type", set to backup or restore.

    "pid": "7958",
    "id": "",
      "dayofmonth": "",
      "dayofweek": "",
      "hour": "",
      "minute": "",
      "month": ""
    "type": "restore",
      "who": "selected"
    "where": "/home/admin/admin_backups",
    "progress": "0.00%",
    "details": "CMD_ADMIN_BACKUP?action=monitor&pid=7958"
      "pid": "1",
      "id": "2",
      "start": "3",
      "type": "4",
      "who": "5",
      "where": "6",
      "progress": "7",
      "details": "8"
    "current_page": "1",
    "ipp": "99999",
    "rows": "1",
    "total_pages": "1"

SpamAssassin: Allow wildcard using regex in exim filter, user rspamd.conf files new

On the User Level -> SpamAssassin Setup page, both the blacklist and whitelist entries now support more detailed wildcards.

For example, you can now use:





including the previous values:




The whitelisted values, if a wildcard is present will rely on exim's "matches" command, instead of the previous "contains" command, and will (for example) look like:

$sender_address matches ".@.\\\\.com\\$"

Where the actual regex is:


but a \ needs to be escaped multiple times for the multiple-level of embedding, so a literal \ ends up being \\\\

And the $ also needs to be escaped so it's not processed (it's a special character in exim strings)



whitelist/blacklist entries would look like:

from = “/.@.\.com/”




whitelist priority from "high" to "4" so that it's a higher priority than the blacklist.

CMD_SHOW_SERVICES?json=yes to include "actions" list for start/stop/restart/reload new

Services Monitor:

Previously, only skins using the older table method of services would know which actions were available.

So any newer skin that relied heavily on json (for example) would not know which actions were available.

This change includes extra json output for the json=yes option for CMD_SHOW_SERVICES

A new top-level array is included in the existing data, eg:


so the unavailable options can now be hidden.

Admin Level: extra files included in Admin backup new

Backing up an Admin account will now include various extra files from /usr/local/directadmin/data/admin

They'll be stored in the "backup/" directory within the Admin backup.

Extra files:


  • a_welcome.txt
  • backup.conf
  • backup_crons.list
  • brute_skip.list
  • cluster.conf
  • cluster_email_allow.list
  • custom_domain_items.conf
  • custom_package_items.conf
  • ip_blacklist
  • ip_whitelist
  • r_welcome.txt

The details on the restoring of these files has yet to be determined, but at least this data will be backed up (eg: handy if you use a login key for DA-to-DA clustering, etc)

FTP/EMAIL: Ability to pass crypted password instead of plaintext new

If you're trying to restore an email or ftp account, but only have the crypt, you can now pass the crypted "passwd" instead.



while still passing the user+domain.

To use this, simply include:


into your POST, and DA will expect the value to be crypted.

DA will check to ensure that it's a valid crypt.

You can also pass a suspended crypt, like "!$..."

Change the 'Compress and Download' to use relative path new

Relating to compress+download of directoreis feature from 1.57.3:

CMD_FILE_MANAGER: one-click download tar, tar.gz, zip archive of directory

This change will alter the internal structure, from always being relative to the User's home directory, to relative to the parent directory you were in when the archive was created.

For example, if you have a folder:


And you're in the public_html directory, and use the feature on the "download" folder, the old way would have had an archive structure of:


This change will alter the internal structure to be simply:


Option to hide version in title (SKINS) new

A new internal directadmin.conf setting:


which includes a global token:


and it's up to each skin to check this and not show it in the header.

Enhanced code:


CMD_MASTER_LOGIN: master access while login-as as User (SKINS) new

If you're a creator (Admin or Reseller) and you "Login As" to one of your created accounts, you previously only had access to data from this account.

This new CMD_MASTER_LOGIN allows queries to certain higher-level data, allowing ease of switching between login without needing to do a Logout and Login.

Can simply switch between accounts with a search.

You must be logged in as another account using the "Login As" function to use this.

Make use of the existing global token:


to know if it does not match |USERNAME|, and you can use it.

the GET search= value can be any text, and a sub-string search of all available Users/domains will be returned, up to the ajax_list_max=20 limit (directadmin.conf)


search=[all]                        - returns ALL available Users/domains. If master is an Admin, this is every account on the server, all domains on the sever
search=[all_users]             - same as \[all\], less any domains.
search=[all_domains]        - same as \[all\], less any accounts.
search=[user]&user=fred   - returns data only for user fred.

Using the [all*] methods will most likely be a slower response time for servers with thousands+ of accounts.

When possible, just use the dynamic-autofill style result with a search every time the User stops typing the search value.

You could alternatively pre-load the full set, and search from that with JS, as long as you're aware of the possible load delays, making the type-search response time faster since it's all client-side.


A fast listing of all visible accounts can be done with:





method: POST



will produce dynamic output.. and if no error, a normal dynamic success will be sent PLUS:


so you'll know if you need to reload an cache (that's up to the skin coder's discretion).

If 0, you'll likely just want to redirect to / to avoid any issues.


The CMD_MASTER_LOGIN call will allow for:

method: GET
search=<your query>

where it will hunt for Account and Domains accessible.

If you see is_reseller_skin >= 1, you'll want to refer to the bottom of this guide for details on how to warn them about the potential security risks of logging in:

Notify if login-as account is not using a global skin (SKINS)(LANG)(SECURITY)

Where "is_reseller_skin=2" means, it's not a global skin, but you'll be getting an override to the same current skin (safe, uses your current skin)

The is_reseller_skin=1 should add a warning about the danger of logging into a Reseller skin, and only do if you want to

(or just set directadmin.conf with: allow_admin_login_as_to_reseller_skin=0, which returns is_reseller_skin=2)


If you only want the info for one specific User, the most efficient way would be to use:

method: GET
search=[user]          #literal string
use=fred                 #where fred is the one and only User you wish to request.  No "searching" is done, it's a direct access and display of data, far quicker than hunting through lists.

Will be 2 arrays, eg:



      "is_reseller_skin": "0",
      "user": "backtest"
      "is_reseller_skin": "1",
      "reseller_skin": "/home/reseller2/skins/power_foofoo",
      "reseller_skin_owner": "reseller2",
      "user": "hacktest"
      "creator": "admin"
      "is_reseller_skin": "1",
      "reseller_skin": "/home/reseller2/skins/power_foofoo",
      "reseller_skin_owner": "reseller2",
      "creator": "reseller2"

As the size of the results can be large, it's highly recommended to use the result pagination.

You can specify the number of results on a page, and the desired page number.

For example, the first load of the page should request:


As the User scrolls, once the bottom approaches, say when the scroller is a given number of items near the end or some similar logic, then load the next page into the list.

NOTE: there is a global |TABLE_DEFAULT_IPP|=50 token, as per the directadmin.conf table_default_ipp=50

When both the pagination_ipp and pagination_page are greater than 0, then the pagination will kick in.

When enabled, DirectAdmin will also include output similar to table info, except per-domain list and per-user list, at the top-level of the json output (not in the "domains" array), eg:


      "is_reseller_skin": "0",
      "user": "testrecur"
      "is_reseller_skin": "0",
      "user": "reseller2"
    "current_page": "2",
    "ipp": "5",
    "rows": "7",
    "total_pages": "2"
    "current_page": "2",
    "ipp": "5",
    "rows": "5",
    "total_pages": "1"

Note how the request is for page 2 (using ipp=5), and this does overlap with the domain list, but not with the User list.

For the User list, the total of 1 page is less than the requested page 2, thus the User result is empty.


BFM to also scan non-default /var/log/pureftpd.log new

By default, pure-ftpd logs to the syslogd, which ends up in /var/log/messages.

If you decide to add log separation by editing the /etc/rsyslog.conf, DA would not know where to look for scanning.

This change simply adds a check to the /var/log/pureftpd.log using the syslogd formatting.

Note, this does not support the direct logging AltLog option that pure-ftpd.conf supports.

You must use syslogd in order for this to work, logging direct to the log files from pure-ftpd would result in the "Apache" style formatting which the BFM is not scanning for.

New internal directadmin.conf setting:


again, requires "syslogd" formatting, and not the "pure-ftpd direct" formatting.

Basically, don't shut off syslogd in the pure-ftpd.conf.. you should have:

SyslogFacility               ftp

~ Compile time: Nov 27 2019 at 16:48:26

One-Time Login Hash URL: redirect_url and auto-enable login keys if needed new

Relating to the One-Time Login Hash URL feature;

One-Time Login Hash URL

this change allows another command line option:


For example, if you need an immediate redirect to a specific plugin within the hash, your creation call might be like:

/usr/local/directadmin/directadmin --create-login-url user=admin redirect-url=/CMD_PLUGINS_ADMIN/hello_world/index.html

which will generate a hash url, as usual, except when you navigate to this url, after it's accepted, you'll be redirected to this above path.

This must be a GET request, since you cannot redirect into a POST (what you do on something.html is up to you)

The JSON output will include:


if it was set.


Also, if the given "user=" account does not have login_keys enabled, any time a --create-login-url is run for this User, login_keys=ON will automatically be set.

The JSON output will include:


should this account be altered.

Since "root > admin", what root wants, root gets.

A system.log entry will be added, eg:

"Authentication::create_one_time_hash: set login_key=ON for admin for login hash."

so you'll know when it happened.

Database: download: raw .sql option new

The CMD_DB call now supports the download of plaintext .sql files, instead of just the user_db.gz files (which is not a tar.gz file).

The ".sql" href will appear next to the "Download .gz" href in the "Download Backup" column of the MySQL Setup page. (may vary on Evolution)

Similar to the call:


this will be:


The upload function on the same page already supports raw .sql upload.


CMD_API_DNS_ADMIN: raw_save: option to re-sign the unsigned zone file to DNSSEC new

The intended purpose for this option is when you're using this DA box as the master dns, but are updating a zone from some remote script, but still wish to have the master DA manage your DNSSEC keys.

Do not use this if this DA box is a a slave, since re-signing the already signed zone may cause unexpected results.

(DA's Multi-Server Setup will transfer the raw signed zone to the slave if the master DA box is using dnssec for this zone)


method:  POST

GET values:


plaintext unsigned zone file

Where the sign_zone=yes being passed in the GET will have DA read the zone after it's written, and re-save it again (without updating the serial during this save),

and where the save internally re-signed the zone automatically.

Note, you must have already generated keys, and signed the zone once through the GUI or other means (this will not do the initial work)

since the raw_save always saves to the un-signed location, not the file (which is handy it works that way for this case).

The custom script, if it exists:


will be called BEFORE the re-read/write/re-sign, in case the save needs to be modified by this script in some fashion, prior to the re-signing.


Restore: side-operations to use unpigz when available new

If the pigz=# option is set in the directamdin.conf with the unpigz binary present, previously, only the main extraction of the full tar.gz was using unpigz.

Now, other areas will use it as well:

  • extraction of full file listing to determine what we have to work with prior to extracting other files

  • extraction of backup/backup_options.list

  • extraction of backup/user.conf

will now use the unpigz when set.

It was found the extraction time is more than 2x the speed with the same hardware vs just tar (using pigz=8 in my case on a VMware box with very high raid read/write speed)

LANGUAGE: mail_settings.html now has internal language tokens from email.txt (TEMPLATE)(LANG) new

When creating an email account, after success, the User will be shown their user/pass, and pop/imap/smtp settings.

This is stored in the template:


The |LANG| token is available, should you want to copy it to the data/templates/custom/mail_settings.html, however this is not a great solution.

This change swaps all English works with |LANG_*| type tokens.

These tokens all live internally in the:


331=Your account %s@%s has been setup. In your e-mail program, use
334=POP/IMAP Server
335=SMTP Server

And the template now looks like this:


<table class=list cellpadding=3 cellspacing=1>
<tr><td class=list2 align=right><b>|LANG_USERNAME|:</b></td><td class=list2>|USER|@|DOMAIN|</td></tr>
<tr><td class=list  align=right><b>|LANG_PASSWORD|:</b></td><td class=list >|EMAIL_PASS|</td></tr>
<tr><td class=list2 align=right><b>|LANG_POP_IMAP|:</b></td><td class=list2>mail.|DOMAIN|</td></tr>
<tr><td class=list align=right><b>|LANG_SMTP|:</b></td><td class=list>mail.|DOMAIN| |LANG_PORT| 587</td></tr>

Meaning you need only setup your language changes in the email.txt, rather than having to copy the template, which is really only meant for swapping ports or records, etc..


rotate_httpd_error_log_global /var/log/httpd/error_log new

Option to include the /var/log/httpd/error_log into the rotate_httpd_error_log* options.

Internal default;


to enable:

./directadmin set rotate_httpd_error_log_global 1

where DA then includes this file, assuming rotate_httpd_error_log_meg is > 0

Also applies to /var/log/nginx/error_log

OpenLiteSpeed creates a new error_log each restart, so this is not likely needed.


Crons: PHP_INI_SCAN_DIR for per-User php.ini (TEMPLATES) new

New option, off by default;


can be enabled

./directadmin set set_php_ini_scan_dir_in_crons 1
service directadmin restart

such that the next time a User saves their cronjobs, DA will add a line such as this to the top of the crontab:


DA will also add the file:


which might look like this:

sendmail_path = /usr/sbin/sendmail -t -i -f
session.save_path = /home/fred/tmp
open_basedir = /home/fred/:/tmp:/var/tmp:/opt/alt/php70/usr/share/pear/:/dev/urandom:/usr/local/lib/php/:/usr/local/php70/lib/php/
mail.log = /home/fred/.php/php-mail.log

and the ~/tmp is created 700 fred:fred if it's not present.




|?SENDMAIL_PATH=/usr/sbin/sendmail -t -i -f \`PHP_EMAIL\`|

sendmail_path = |SENDMAIL_PATH|
session.save_path = |SESSION_SAVE_PATH|
open_basedir = |OPEN_BASEDIR_PATH|
mail.log = |CLI_PHP_MAIL_LOG|

Extra suspension details (JSON) new

When suspending a User with:


You can now also include:

details=any details you want about it

which will write that string to:


When unsuspending, this file will be deleted.


This data will be included with the call to:


Where the json table will now have a 4th column in "some cases" (on this case for now), called "extra" with "details" inside.

Sample output:

  "setting": "suspended_reason",
  "usage": "spam",
  "max_usage": "Spam",
    "details": "Fred is the biggest spammer I know.\"

where you'd be looking for the "setting" == "suspended_reason" for this extra['details'] value.

The "37" is not usable as it might change if other entries are added before it.


The "Details" from this "extra" should be shown at:


in the "User Suspended" table, if this extra['details'] index is present, perhaps in the "Reason" value cell, after the selected reason (should be enough space there?)


GUI for hot-link protection, so Users don't need to manually edit their .htaccess files.

Universal interface also setup openlitespeed and nginx configs correctly.


Users will be able to access the GUI in Enhanced skin via:



New file:


Appended file:



URL added to:


below "Site Redirect"










Hotlink code:




Added to the following templates using this token:













Get data:


Where the "data" array is the current list of allowed referer urls.

Standard table rules, which can be sorted/search, etc.

The "options" relate to tokens usend in Enhanced to enabling checkboxes, radio-boxes and input fields.

The "MY_DOMAINS_LIST" refers to the "Allow my domains to the list" button, which would be used to add a bulk-list of items to the list, for adding.

See the data/skins/enhanced/user/hotlnk.html for javascript used to do this.

      "url": "http://*"
      "url": ""
      "url": "https://*"
      "url": ""
      "url": "1"
      "current_page": "1",
      "ipp": "50",
      "rows": "4",
      "total_pages": "1"
    "HOTLINK_PROTECT_FILES": "jpg,jpeg,gif,bmp,ico,svg,png",
    "MY_DOMAINS_LIST": "\\\\\\https://*\\\http://*\\\\\\\\\https://*\\\http://*\\\",
    "REDIRECT_TO_URL_CHECKED": "checked",
    "REDIRECT_URL": ""

Delete URLS:

method: POST

Save settings/URLs

method: POST
enabled=yes|no                              - checkbox, thus absence implies no: Used for on/off switch into webserver configs.
allow_blank_referer=yes|no           - checkbox:  When on, a blank referer (direct URL in browser without click) is allowed.
urls=<list>                                      -  list of URLs, with http/https, one url per line. May contain trailing /file.html if desired.
files=<list>                                      - list of file extensions, comma separated, no spaces (DA will trim spaces) to apply to hotlink protection.

RETURN value is standard dynamic output success|error:text

CMD_API_SHOW_USERS: All Users plus any variable from user.conf new

Extended the functionality of the CMD_API_SHOW_USERS command.

By default, this will return a list[] of usernames.

If you want to get the "date_created" variable for all Users under your control (Resellers can call this),

you can simply request that variable by using:


so the "get_varilable" name can have any value from the user.conf file.

Must be a very basic value: a-zA-Z0-9_

The return will be a list, eg:


mysqldump --routines and DEFAULT CHANGE: restore_database_as_admin=1 new

New directadmin.conf option:


which ONLY applies when running a backup from Admin Level -> Admin Backup/Transfer

When DA starts up, if restore_database_as_admin=0 is set, then mysqldump_routines=0 will be set.

This will allow an admin to do a full backup/restore with routines and functions by default.

Previously, the admin would need to setup extra_backup_option=--routines which is no longer needed.

Again, this only applies to the Admin Backup/Transfer.

When using the --routines option, the mysqldump will add;


which requires SUPER PRIVILEGE to restore it, hence the change only applies to Admin Level at the moment.

There is a --skip-definer in MySQL 5.7.8+, but as it's not in MariaDB at all, we have not explored this option for the moment.



Was previously 0.



/etc/virtual/domainips to show ipv6 as 2nd owned IP new

Relating to the option to allow domains their own outbound IP for email, (and their own inbound HELO smtp value):

DA to manage domain IPS file for exim outbound IP/interfaces

this feature will extend the /etc/virtual/domainips file, such that, if you have an owned main IP on the domain (which should already be added to the domainsips file, when enabled),

this will first do a lookup of all linked IPs on that owned IP. If there are any linked IPs, that will be the 2nd list, and if there are 0 linked, it will read the domain's ip_list file for the 2nd list.

Once a list is obtained, the main IP type is checked, and the list is search for the "other" type.

Eg, if the main owned IP is an IPv4, the type of the 2nd IP to be searched for will be IPv6, and vice-versa: if the main is IPv6, the 2nd is IPv4.

If a 2nd is found, then the /etc/virtual/domainips file will hold, for example:; fe80:0:0:0:10c:18ff:fc1a:638a

If you're using the lan_ip option, this feature will not have any effect, since the external IPs cannot be bound to.

Admin Restore: /home2 override partition choice (SKINS) new

Relating to the directadmin.conf option:


Ability to override default /home directory (SKINS)(LANG)

When set, DirectAdmin will show you these choices in "Step 2" of the restore, allowing an Admin to restore accounts to a specific partition.


When issuing the restore, you can optionally pass:


to have DA restore to this partition (requires it to be set in the home_override_list)


Any task.queue restore process for an Admin Level or Reseller Level restore which contains the create_user_home_override=/home2 value,

will be set internally for the restore to create any account on this path.



in the "Step 2: Select IP", added another <tr> entry:

<td class=listtitle></td>
<td class=listtitle>



in the top-level array CMD_ADMIN_BACKUP?json=yes output:

    "selected": "yes",
    "text": "/home",
    "value": "/home"
    "text": "/home2",
    "value": "/home2"

T22902 Include short hostname in /etc/hosts new

The script:


has been updated to include the short hostname format in the /etc/hosts file.

Old format in /etc/hosts

new format: server

CMD_WIDGETS: add a widget to all/selected accounts new

New option for the CMD_WIDGETS command, only for Resellers or Admins,

to allow them to add or remove selected widgets from all accounts under their control, in mass.



Method: POST
level=user|reseller|admin   - blank defaults to user



where you must pass at least 1 widget, but multiple are allowed.

You can either specify who=all, or if you use who=multiple, then specify the list of accounts with user0, user1, etc..

When a Reseller specifies who=all, it's the full user.list file (all Uses below their control)

When an Admin specified who=all, if no type is passed, it's that give widget "level" for all accounts, where applicable.

But if type=admin is passed, the list is taken from the admin.list, and type=reseller from the reseller.list.

When type=user is passed by an Admin, then full data/users/* list is obtained, and the admin.list and reseller.list is subtracted from it.



Method: POST
level=user|reseller|admin   - blank defaults to user



See the "add" step for behavior of each item, of course the only difference being that the widget* values would be removed from all accounts at the given level, rather than being added.



for both add and remove, the output is in JSON, as:

"success": "text"


"error": "text"



Option for Reseller's own User limits to be included in their own allocation total new

New directadmin.conf option, off by default internally:


Where if you set it to 1:

./directadmin set reseller_allocation_include_self 1

service directadmin restart

The Reseller's own user.conf limits will be counted towards their total allocation.

The default is such that allocation only counts the User limits of User accounts this Reseller has created.

The reason we do not enable this by default.. and honestly do not recommend it's use,

is that when a Reseller is created with 1G disk usage (for example), the Reseller's user.conf limit is also 1G

As such, the max allocation is hit immediately when the Reseller account is created.

The result is that, if overselling is turned off, the Reseller must first lower their own User limits before they'll be able to create any Users, hence it was never considered to be included,

hence we don't recommend it as it will be annoying to work with, the moment it's turned on and Reseller's try to create their first User.

DA does its best to keep the allocation count correct, but the cleanest/full recount only happens during the nightly tally.


To manually trigger a full tally, run:

cd /usr/local/directadmin
echo "action=tally&value=all" >> data/task.queue; ./dataskq d2000

This will happen automatically every night, so running it manually is only needed to check if the count is correct or not.


SpamAssassin: user_prefs safe-area for manual code additions (TEMPLATES) new

DirectAdmin uses the data/templates/user_prefs templat file for all writes of User ~/.spamassassin/user_prefs files.

If any changes were manually added, any click of the "Save" button would overwrite your changes.

This change adds a safe area, in between 2 tags:

#SAFE AREA start

where you can place your code in there.

Just before any save, DA will re-read the client's user_prefs file, hunting for these tags and will save any code in between them.

That code will be added to a token SAFE_CODE, which has been added to the user_prefs template.


EXISTING custom user_prefs files

In order to keep your current custom code safe (eg: custom scores), wrap it with these tags:

#SAFE AREA start


Option to include 2222 failed attempt in BFM blocks (CSF) new

All of the below assumes the option in Admin Settings is enabled:

"Blacklist IPs for excessive DA login attempts" = yes

New optional feature will go beyond just adding the IP to the ip_blacklist file, but when below is enabled, it will also add the IP to the firewall using the scripts.

New internal default, disabled by default for now:


To enable, set to 2:

./directadmin set include_directadmin_port_in_brute_firewall 2
service directadmin restart

Where the values are as follows:

include_directadmin_port_in_brute_firewall=0: Rely on ip_blacklist to accept the connection and return "Your IP is blacklisted" when blocked.

include_directadmin_port_in_brute_firewall=1: When too many failed login attempts are made, add to firewall (in addition to ip_blacklist)

include_directadmin_port_in_brute_firewall=2: When too many failed login attempts OR unauthorized connections are made, add to firewall (in addition to ip_blacklist)

To prevent a DOS, 2 is the preferred method, as it blocks both brute force login attempts, but also blocks IPs that connect to the box only to clog up your child processes.

The unblocks will happen with the same ip_blacklist unblock timings (via dataskq), but will also unblock from the BFM firewall, again use the scripts.

Show All Users: Per-User Pointer counter new

On the "Show All Users" page, each pointer is listed below it's domain, tagged with a "P:" to signify it's status as a pointer.

This optional feature let you's set:

./directadmin set show_pointers_in_list 2

(where 1 is the internal default from the existing show_pointers_in_list feature)

such that setting show_pointers_in_list=2 will add a counter beside each P, counting up for all Pointers for that given User.

For example:

This allows you to quickly see, at a glance, how many domain pointers a User has, without need more columns.



was added in February 11, 2020.


Compile Time: Jan 6, 2020: 2:15pm

Only show meaningful days remaining in bandwidth usage notice (TEMPLATES) new

The template file:


has been changed to only show the |TIMELEFT| notice, if the number of days before the bandwidth runs out is less than 31 days..

and if the bandwidth limit is not unlimited.

2 new tokens are now available:

BANDWIDTH_MAY_RUN_OUT=0|1 DA will decide this value for you based on the above logic.

DAYS_REMAINING_THIS_MONTH=1-31 Number of days left in the month.

The message has been changed to be:

|*if BANDWIDTH_MAY_RUN_OUT="1"|It is estimated, at the current rate of use, that the account bandwidth will be used up in |TIMELEFT| days, at which time the account may be suspended for the remainder of the month.

There are |DAYS_REMAINING_THIS_MONTH| days left before the bandwidth usage is reset.|*endif|

Compile Time: Jan 7th ~23:18

OpenLiteSpeed: DA to use the CustomBuild ssl_configuration setting for sslProtocol (TEMPLATES) new

This feature will have DirectAdmin use the newer CustomBuild 2.0 options.conf setting:


where intermediate is the default you'd likely already be using.

The default sslProtocol with OLS is now 24, set in the 2 templates.

The 24 value is for TLS 1.2 and 1.3 only.

The previous 30 is for older TLS versions (1.1 and before)

This change will set it to 30 in both the listeners.conf and each User's openlitespeed.conf if you have:


set in your options.conf.





Compile time: Jan 12 2020 at ~20:24:07

Option: login_keys_notify_on_creation=2 for locked on new

New change which allowed calls to decide if the login key/hash Message System notice should be sent.

A) When directadmin.conf login_keys_notify_on_creation=1 is set (internal default), the following rules apply:

  • creation of the command-line "--create-login-url" will not send a notice to avoid client confusion.

  • The command-line call can also pass login_keys_notify_on_creation=0|1 to decide if a notice should be sent.

  • any socket call, including APIs or command-line can pass login_keys_notify_on_creation=0|1 via POST to decide if a notice should be sent.

B) However, if you set directadmin.conf to have login_keys_notify_on_creation=2:

  • all socket/2222 requests to override the default will be ignored, and a notice will always be sent.

  • The command-line method notices will still be off, but can still override using login_keys_notify_on_creation=0|1

C) For login_keys_notify_on_creation=0, then no action will generate a notice. Any call type can override the desired behavior to send one, if needed.

Hook: phpMyAdmin SSO: new

New optional hook to use with the phpMyAdmin one-click login feature:


Hook will be at:



token=<token name>
username=pma login name
passwd=secret pass, known to no-one, except internally in DA, set in DB User account.

Note that name= may be set to username\_%% in the event that all User databases are to be accessible for this token.


The exit code should be 0 for success and 1 for error,

however error or not, the behavior will not be altered and the output will not be modified.

The only variance is a debug output at level 100 will provide info from your script.

This is basically just a silent script to take action as needed.


Compile time: Jan 13 2020 at ~13:38:43

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

Previously all hooks needed to live in:


for various hooks, like or, etc.

This feature allows (and we now recommend) using a per-hook folder name, instead of a script name.

Let's use the as an example.

The script at the old location is still valid:



  1. However, if you're not a human and using an automated installer, say your program is called foo, the recommend path for your copy is now going to be at:


Where the script filename is taken from the original path, the .sh is dropped, creating a new folder name.

All scripts that reside in this "user_create_post" folder, regardless what they're called, will be run.


  1. Additionally, plugins can now provide their own hooks, in their existing path:


where the full .sh script should be used.



The passed variables will remain the same as before, for all scripts,

with the exception that, should any script run before this script have generated a "result" (exit 0) or "error" (exit 1), those strings would be passed via ENV, but you'd basically just ignore them regardless.

If 2 scripts both throw errors/results, their outputs will be appended, so no output should be lost.

All scripts will be run, even if one returned a non-zero result.

If any one script returns an error, then the entire set of hooks will be an error (eg: would abort if any of them return non-zero)

The order of execution would be:

  1. scripts/cusotm/hook_name/*.sh

  2. scripts/

  3. plugins/*/hooks/

Compile time: Jan 14 2020 at ~18:11:58

The tool at:

Admin Level -> IP Manager -> -> Link IP

has the option to "Apply to existing Domains".

If you have many domains, it's possible that the foreground could hit a timeout leaving the task in an unfinished state.

The change for this feature is to offer the option, checked/enabled by default in the form:


When this is set, the add link task is processed in the background via task.queue.

It may take up to 1 minute before the processing starts, and an unknown amount of time, depending on the number of websites present.

Ability to shut off certain features of the File Manager: upload, download, download and compress new

This feature is an extension of this feature, which already exists:

Ability to shut off certain features of the File Manager

The feature adds 3 more bit-wise numbers to disable uploads, downloads, and the "download and compress" option, respectively:

#define FM_F_UPLOAD 8192
#define FM_F_DOWNLOAD 16384

The full list is now:

#define FM_F_RENAME 2
#define FM_F_COPY 4
#define FM_F_RESET_OWNER 16
#define FM_F_EDITABLE 128
#define FM_F_EXTRACTABLE 256
#define FM_F_DELETE 512
#define FM_F_CHMOD 1024
#define FM_F_MKDIR 2048
#define FM_F_CLIPBOARD 4096
#define FM_F_UPLOAD 8192
#define FM_F_DOWNLOAD 16384

See the id=2308 feature for more info.

Compile time: Jan 17 2020 at ~15:48:05


Admin Settings: JSON: Manage E-Mail blacklist_usernames, blacklist_smtp_usernames, blacklist_script_usernames, blocked_authenticated_users new

Relating to this guide on how to fully block outbound email from any given User/Email:

this feature adds a front-end GUI for it in the Admin Settings page.

It's JSON only so for the time being, it will only be visible in Evolution (not Enhanced)





For all 3 lists, they'll only show up and be useable if the output from:



email_settings["HAVE_BLACKLIST_USERNAMES"] == "yes"

When true, 3 new array will show up, eg:

    "emailtest": "1579479488"

Where each of the 4 lists could contain any Username on the system OR any E-Mail address on the system.

The blacklist_script_usernames only makes sense to add Usernames (adding E-Mail accounts to it doesn't do anything)

Showing the 4 files could in theory be shown in 1 table, with 5 columns:

User/Email, bl_usernames, bl_smtp, bl_scripts, bl_auth_users

emailtest, yes, no, no, no

for example, or something along those lines.


blacklist_usernames: blocks anything, authenticated or script based from this User from sending any email.
blacklist_smtp_usernames: blocks these smtp-auth accounts from sending any email (script will still work)
blacklist_script_usernames: blocks any scripts owned by this DA username from sending any email (smtp will still work)
blocked_authenticated_users: BlockCracking: temporary block on user/email from smtp-auth sends. Password can be reset to clear the block. See "BlockCracking notices and unblocking" for more info.
  This file will only show up if BlockCracking is enabled (if /etc/exim.blockcracking/auth.conf exists)


method: POST


  • the action must be one of those 2 values

  • you can select at least 1 and at most 3 of the blacklist_* files to add to / remove from

  • select0, (select1), must be 1 or more and can be local DA accounts OR any email local email account.

  • The blocked_authenticated_users should automatically be filled by BlockCracking.

Compile time: Jan 19 2020 at ~18:57:52


Email User exigrep smtp-logs user_email_smtp_logs=2 to span multiple mainlogs new

Related to the feature:


Option to disable User access to per-email smtp logs

If you change this to be:

./directadmin set user_email_smtp_logs 2

to a value 2 or higher (<1000), then DirectAdmin's call to exigrep will change from

exigrep ID /var/log/mainlog

to instead be:

exigrep ID `/bin/ls -t /var/log/exim/mainlog* | /usr/bin/head -n 2`

where 2 is swapped with the value set.

The default is still 1, so you must set this to 2 if you want logs to go back further, at the cost of slower exigrep performance.

NOTE: running exigrep on multiple mainlog does not maintain log order. The timestamps in the output are not always sequential, regardless of the order of the logs fed into it.


Compile time: Jan 19 2020 at ~20:36:21

Create User: DA can now select a random free IP new

Expanding on this feature where a randomly selected shared IP can be chosen:

Create User: DA can now select a random shared IP

this feature lets you pick "Free - randomly selected", where DA will pick an IP for you.

The ip value is:



Compile time: Jan 20 2020 at ~16:49:20

Ability to add extra variable columns to Show All Users, List Users new

New feature, internal directadmin.conf defaulting to:


Where you can modify this value, colon separated, to add more fields, eg:

./directadmin set show_all_users_cache_extra_vars date_created:mysql
service directadmin restart

which would mean both "date_created" and "mysql" columns are appended to the Show All Users and List Users pages.


DirectAdmin stores these values in the show_all_users.cache file, so in order for it to be viewed, you must either wait for the nightly tally to run after setting the variable,

OR force a cache update "now" with:

cd /usr/local/directadmin
echo "action=cache&value=showallusers" >> /usr/local/directadmin/data/task.queue; ./dataskq d2000


When adding a variable to the show_all_users_cache_extra_vars list, how it's shown depends on if it's in the user.conf, user.usage, or both.

If it's only in one or the other, then that value is simply taken from the given file and place into the cache.

If the variable is in BOTH user.conf and user.usage files, then the value is stored in the show_all_users.cache with the usage/limit format, eg:

mysql=1 / unlimited

for example.


Hide directory usage: filemanager_show_directory_count=0 new

New internal default:


where you can disable the feature:

./directadmin set filemanager_show_directory_count  0

in order to fully hide the values for directories in the "Size" column.

The cells will be fully blank, which helps avoid confusion when filemanager_du=0, meaning a recursive folder size is not computed, thus shows the directory size of "4.0KB", when it may in fact have data in it.

Often, you'd want to hide this size as could cause confusion. This feature will hide it for you.

Note, when setting filemanager_show_directory_count=0, this does also disable the recursive count, as if filemanager_du was set to 0, to improve performance.




will return:

"showsize": "",

instead of the human readable "4.0KB".

The "size" will always return the true bytes.

So if you're not using the "showsize", then please check the table value:

info["fm_settings"]["filemanager_show_directory_count"] = 0|1

to see if you should hide the cells for directories (when set to 0)



Compile time: Jan 22 2020 at ~13:55:18

ListSpeed: CacheRoot lscache (TEMPLATES) new


The 4 main apache virtual_host2*.conf file will contain:

|*if WEBSERVER="litespeed"|
<IfModule Litespeed>
CacheRoot lscache

Where "WEBSERVER" is a new token, containing one of:


For nginx_proxy, it will contain the web server type the template is being written for.

So the nginx_proxy nginx templates will have WEBSERVER=nginx, and the nginx_proxy apache templates will have WEBSERVER=apache

Include mysqld/mariadb restart for systemd fixed

Previsouly, the services monitor relied on the output of the /etc/init.d/mysqld script to see which actions were available.

Since those paths would not have existed on a systemd box, the reload and restart options would have been hidden.

This simply enabled the restart action for mysqld on systemd box (which is basically just a stop/start anyway).

Reload is typically still not available for mysql/maridb anyway, so reload will not be an option.

Dovecot SNI: Optimize local_names into single blocks for faster reads (TEMPLATES) fixed

The files in:


Could end up with many local_name entries, should there be many hosts.

Changed the formatting to only use 1 local_name per domain, and instead list all hosts sequentially in that local_name entry.

This means only one instance of that cert would be used, rather than multiplied by the number of hosts (eg: each subdomain, etc)

Also introduced caching on the dovecot_sni.conf template over the span of this binary instance, so any full rewrite should go much quicker, and dovecot should restart much quicker as well.


TASK QUEUE (not new, just handy to have here if you want to rewrite all dovecot sni files)

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

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

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

Static Linux 64 binaries to hunt for correct binaries fixed

The "Generic Linux 64 static" binaries in the OS selection are compiled on CentOS 6, 64-bit.

You can install them on Debian, however, some of the hardcoded paths are not correct.

This change will replace the static defines for these paths to static pathExists("a")?"a":"b" type checks, for CentOS (a) else Debian "b",

where permissions are escalated prior to calling the define var, to ensure the path check actually works.

For most cases, the files in question would be world-readable, both other cases like /var/log/maillog vs /var/log/mail.log, root is obtained first, to be sure.

SDOCROOT: The --DocumenRoot option CMD_DOMAIN?action=document_root to use correct token fixed

Relating to the subdomain template change to use SDOCROOT, instead of DOCROOT/SUB:

Subdomain DocumentRoot override file (TEMPLATES)

the 2 features that output the list of DocumentRoots needed updating:

JSON: Show list of document roots for all domains under this User

It now use the final SDOCROOT token in the output for subdomains.

Rspamd: task.queue rewrite required_hits vs required_score fixed

Bug during a task.queue rspamd rewrite where the user_prefs file will correct read in the required_score.

However, this needed to be converted to required_hits for the template write, so the default value of 5.0 never got overwritten.

Saving through the GUI was not affected.

Nginx: pointers_own_virtualhost=1 incorrect (TEMPLATES) fixed

Relating to new feature allowing per-VirtualHost entries for each Alias domain pointer:

Alias Domain Pointers to use their own VirtualHost (TEMPLATES)

bug with the nginx templates using DOMAIN instead of HOST_DOMAIN for the server_name.

Debian: named_rndc_addzone uses 3bf305731dd26307.nzf fixed

By default the:


feature (off by default), will be looking in _default.nzf for a listing of all rndc added zones, when showing all zones.

Some boxes (aka: Debian) use 3bf305731dd26307.nzf instead.

Instead of doing an OS check, this will simply see if ${nameddir}/_default.nzf does not exist and if {$nameddir}/3bf305731dd26307.nzf does exist, and if yes, will use ${nameddir}/3bf305731dd26307.nzf for the path.

Another check is done, if ${nameddir}/_default.nzf does not exist, but /var/cache/bind/3bf305731dd26307.nzf does exist, it will use /var/cache/bind/3bf305731dd26307.nzf.

So if your rdnc zone adding tool puts data in either 3bf305731dd26307.nzf, ensure that the _default.nzf does not exist.

The only case where the nzf file is used, is the listing of all zones.

All other zone checks/changes are done directly with the name.

CREATOR token in dns_*.conf templates not set in some cases fixed

But where the |CREATOR| might not be set when using the dns_*.conf.

Logic changed around to always be set when possible.


If a domain does not have ssl enabled, do not LetsEncrypt auto-renew fixed

During the nightly tally certificate checks, LetsEncrypt will check to see if a certificate is about to expire.

Previously, the domain's ssl setting was not considered, and a renewal was attempted even if it's off.

Usually, you'd use the "Disable Auto-Renew" button to clear the LetsEncrypt renewal files, so it doesn't auto-renew..

but if you didn't delete those, and did disable SSL, then this will skip the given domain for auto renewal, even if the files are still present.


New admin: missing domains/sharedip fixed

Upon new admin creation, the ~/domains/sharedip was not created.

Bug was introduced Nov 11, 2016 with introduction of templates/custom/suspended override for new admins.

Very large file upload timeout on 'parse content' fixed

Increase the alarm() timeout time during the parsing phase of an upload to use a dynamic amount of time, scaled to the size of the upload, but still capped to a reasonable time.

The value is still scaled relative to the "timeout=60" value, whatever you have set, so regardless of what's set, increasing the "Timeout" in the Admin Setting will give any alarm() value a higher time.

Note, any multi-part form data (MPFD) requests are uploaded to a temporarile file descriptor opened by tmpfile(), location determined by the system.

In our tests, this opens in /tmp but the file is not visible within /tmp (hidden file). For this case your /tmp folder must be large enough to accommodate the large uploaded data.

Database: revoking all privileges hides the User fixed

Previously, only database server versions that used the "mysql_use_new_user_methods=1" (which is set internally upon socket connection) would check for the number of privileges.

This change now always requires at least one privilege, since the setting of user permissions always uses the GRANT/REVOKE for all versions, instead of the older "REPLACE INTO mysql.db" setting (as of ~1.56.4)


Should you try and set all DB User privileges to "N", DA will reply with:

An error has occurred while trying to set privileges:


Revoking all privileges would delete the User from the mysql.db. Please grant at least 1 privilege.



CageFS: /var/log/user_logs/user to be 750 root:user and not deleted fixed

When a User with CageFS needs to read their logs for webalizer/awstats, the logs needs to be readable by the User, since webalizer/awstats would be run as that User.

The issue is that everytime the /var/log/user_logs/username directory is created, a call to:

cagefsctl --remount username

is needed, which is fine on it's own, but it has the undesired effect of killing all child processes.

The solution, only when cagefs is in play is to not delete the /var/log/user_logs/username folder, but only to delete the logs from it once done computing the stats.

To ensure security remains, the ownership of the folder has been changed around, and the folder will be left until the User is delete, far in the future.


Compile time: Jan 13 2020 at ~23:03:53

Linux Static binaries: path fixes fixed

The static binaries are compiled on a CentOS box, so the default are compiled in, accordingly.

When installing these binaries on a Debian system, more path checks are needed.

  1. Database Socket path:


When compiled static, an extra check will be done on this file.

If it does not exist, but /usr/local/mysql/mysql.sock does exist, then it will be used instead.

/tmp/mysql.sock is the 3rd fallback.

If the /usr/local/directadmin/conf/mysql.conf has a "socket=" value set, this value will be respected, and no other checks will be done.

Ability to specify the mysql.sock

but should not be required after this fix, if the above paths would solve the incorrect socket location.

  1. Change Password:

passwd --stdin

With Debian, we use chpasswd, so when static binaries are comipled, a literal check for /etc/debian_version will be done to decide if it should use chpasswd, instead of the CentOS passwd call.


Compile time: Jan 15 2020 at ~16:01:45

JSON: Admin Stats: Empty stats for a language other than English fixed

Changed the json indexes to be hard-coded, rather than using the "df" header title's as the indexes.


for "disk", eg:

  "Filesystem": "tmpfs",
  "1-blocks": "4015548",
  "Used": "0",
  "Available": "4015548",
  "Capacity": "0%",
  "Mounted on": "/dev/shm"

NOTE, the above is for bytes=yes. Without that, the 1-blocks become:


Compile time: Jan 15 2020 at ~22:49:32

phpMyAdmin SSO: multiple access_hosts and revoke fixed

Bug with the phpMyAdmin one-click single-sign-on (SSO) feature where the conf/mysql.conf settings for:


were not being respected.

Fixed is to add each acecss_host*= setting to the da_sso_* accounts when created.

2nd bug (possibly only affected with the new hosts), but when revoking the da_sso_* accounts after their expiry, it required the @'' host for each host under this account.

Compile time: Jan 20 2020 at ~15:22:28

mail_partition not being used in /etc/virtual/ (TEMPLATES) fixed

The 2 filters in /usr/local/directadmin/data/templates

  • filter_filterspamfolder
  • filter_userspamfolder

Were referencing ~/imap from the |HOME| token, which is taken from /etc/passwd.

When the mail_partition override is used, say:


the fix is to change any reference to |HOME|/imap to now be:


where the new MAIL_PARTITION_HOME token will contain the usual HOME token (/home/username), unless mail_partition=/home2 is set (for example), in which case, it will hold:


The filters now look roughly like this:

        if "${if exists{|MAIL_PARTITION_HOME|/imap/${domain}/${local_part}}{yes}{no}}" is "yes"
            save |MAIL_PARTITION_HOME|/imap/$domain/$local_part/Maildir/.|INBOX_SPAM|/new/ 660
            save |HOME|/Maildir/.|INBOX_SPAM|/new/ 660

where the ~/Maildir is still relative to the true HOME, and ~/imap is now relative to MAIL_PARITION_HOME (eg: /home2)


You can issue a filter rewrite with this command:

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

to redirect the ~/imap path to the correct $mail_partition/$username/imap path.


Segfault on backup if ethernet_dev not loaded fixed

When running a backup, DirectAdmin will try and put the server IP into the backed up user.conf file:


The function used to get this IP could return NULL, should the ethernet_dev (from the directadmin.conf) not be loaded int the network device.

The result is a serverip=(NULL) setting (value ptr is zero), causing the segfault during the security check before the write.

Added checks in many areas:

  1. security check won't segfault on null anymore

  2. the serverip won't be added to the user.conf if null was returned


Compile time: Jan 24 2020 at ~14:53:44

Last Updated: