In short: On a server running Virtualmin, run the list-domains command with –name-only as root and it prints every hosted domain, aliases included; the –alias option does the opposite and keeps only aliases. Without Virtualmin, read the names from the Apache or nginx configuration.
Webmin itself has no list of hosted domains. The list belongs to Virtualmin, the hosting layer built on Webmin[1], and its virtualmin command prints it. This guide gives the command, the filters that narrow it, a way to export the result to a spreadsheet, and what to do on a server that has Webmin but no Virtualmin. Tested on: the Apache and nginx commands were run on Apache 2.4.69 and nginx 1.30.6. The Virtualmin commands are checked against the Virtualmin 8.3.0 source and documentation and were not run on a live Virtualmin server.
Before you run anything: none of these commands changes Virtualmin or web-server settings, but the CSV export creates or overwrites a file called domains.csv, and the detailed Virtualmin output can contain passwords in plain text (see the warning in the export section). Run them at your own risk. iLogr cannot see your server and accepts no responsibility for what happens on it.
How do you list all domains in Virtualmin from the command line?
Log in to the server over SSH as root and run:
virtualmin list-domains --name-only
The command must run as root[2][3]. You get one domain name per line, for every kind of virtual server: top-level domains, sub-servers, sub-domains and alias domains[3][4]. The order is by owner username and then by creation time, not alphabetical[4], so add | sort if you want an alphabetical list:
virtualmin list-domains --name-only | sort
Without --name-only, the command prints a table with a Domain, Username and Description column. That table cuts each column off after 30, 15 and 30 characters[4], so a long domain name comes out shortened. Use --name-only whenever the names themselves matter.
Does the –alias option include alias domains?
No. It does the opposite, and an earlier version of this guide had it wrong. --alias is a filter that keeps only alias domains[4]. This command therefore lists only the aliases on the server:
virtualmin list-domains --name-only --alias
Plain --name-only already includes aliases. If you want every domain except the aliases, use --no-alias.
Which options narrow the list?
Each option below is a filter, and filters combine: a domain has to pass all of them[4]. Virtualmin's own page for the command lists the full set[3].
| Option | Keeps |
|---|---|
--toplevel |
top-level virtual servers only: no sub-servers, sub-domains or aliases[4][5] |
--no-alias |
everything except alias domains |
--alias |
alias domains only; --alias example.com keeps the aliases of that one domain |
--user NAME |
domains owned by one account |
--enabled or --disabled |
active or suspended domains |
--with-web or --with-ssl |
domains that have a website, or an SSL website |
--with-feature dns |
domains with a given feature switched on, such as dns or web |
--plan NAME |
domains on one hosting plan |
--ip ADDRESS |
domains using that IPv4 or IPv6 address |

For example, the active top-level domains, in alphabetical order:
virtualmin list-domains --name-only --toplevel --enabled | sort
How do you export the list to a spreadsheet?
Names alone paste straight into a spreadsheet. For more columns, ask for JSON and let jq (install it from your distribution's packages if it is missing) turn it into CSV:
virtualmin list-domains --json | jq -r '.data[] | [.name, .values.type[0], .values.username[0]] | @csv' > domains.csv
This writes one row per domain with the name, the type (Top-level server, Sub-server, Alias) and the account username, quoted so that commas inside a value do not break the file[6][7][8]. --json is in Virtualmin 8.3.0[6]; on an older release, run virtualmin list-domains --help to see whether your version has it. JSON output needs the JSON::PP Perl module[7]. Under the hood, --json is the plain --multiline output turned into a data structure, with each label written in lower case and underscores[7]; that is why the type sits at .values.type.
Warning about passwords. The detailed --multiline and --json output can include the domain's password and its database passwords, in plain text when the server stores them that way[4]. Keep the jq filter above (it picks three fields), do not save the raw output, and do not paste it into a ticket or a chat.
From another machine, the remote API can return the same data over HTTPS. Pass program=list-domains, multiline=1 and json=1; with json=1 alone you get the plain table inside a single text field, not the structured list[7][9]. The documentation describes master-administrator authentication, while the 8.3.0 source also lets resellers and domain owners with the remote API permission list their own domains[6][9]. Check what your installed release allows, and protect the credentials.
How do you list domains without Virtualmin?
Webmin alone does not know about hosted domains; the names live in the web server's configuration. Inspect the web server's configuration files.
Apache. On Debian and Ubuntu use apache2ctl -S, elsewhere httpd -S or apachectl -S. It prints the virtual hosts parsed from the configuration files[10]. To pull out only the names, one per line:
apachectl -S 2>&1 | awk '/\(.*:[0-9]+\)$/ { for (i = 2; i <= NF; i++) if ($i ~ /^\(/) { print $(i - 1); break } } /^ +alias / { print $2 }' | sort -u
The report's layout varies. An address and port with several virtual hosts lists each as namevhost with its aliases underneath, but an address and port with a single virtual host appears as one plain line, and in a test its ServerAlias names were not printed at all. The filter above handles both layouts, yet an empty result does not prove that Apache serves no site, and a lone virtual host's aliases are only in its configuration file.
nginx. nginx -T tests the configuration and prints the configuration files, including the files they include[11]. This filter joins the lines, cuts the text at each ;, { and }, and prints the names after every server_name, so names split over several lines and directives sharing a line are both caught:
nginx -T | sed 's/#.*//' | tr '\n' ' ' | tr ';{}' '\n\n\n' | awk '$1 == "server_name" { for (i = 2; i <= NF; i++) print $i }' | sort -u
The test messages and any configuration error stay visible in the terminal, because only the configuration text goes through the filter. If the test fails, nginx prints no configuration and the list is empty, so fix the error first. This is a text filter, not a parser: a regular expression name that contains braces, for example, will be cut short.
What the output looks like. In a test with two virtual hosts and three names for the first one, both commands printed the same four names. Three things needed care. Apache printed a lone virtual host on a port of its own as a plain line, without its aliases. A line-by-line nginx filter missed names split over several lines and directives that shared a line; the joining filter above caught both, and the names from an included file. And a server block with server_name _;, another with a wildcard and a regular expression, added _, *.example.net and ~^( to the list[12]. Those are catch-all, wildcard and pattern entries, not hosted domains, so remove them by hand. These commands read configuration files on disk, which can differ from the configuration currently serving requests after an edit or a failed reload. Files outside the configuration's include tree will not appear.
What can you do with the list next?
A list of names is a starting point. Two checks make it useful before a clean-up or a move to a new server:
- Which names still point at this server? Compare each name's address with your server's. This loop queries each top-level domain and prints the name followed by the last line of its short A-record answer; an empty answer leaves the address blank[13]. It does not show every address when a name has several A records, and it does not check IPv6:
virtualmin list-domains --name-only --toplevel | while read -r d; do
printf '%s %s\n' "$d" "$(dig +short A "$d" | tail -n 1)"
done
- Which accounts are switched off?
virtualmin list-domains --name-only --disabledlists the suspended ones.
DNS only shows where a name points. A name that points elsewhere is probably unused here, but a name that points here may still have no visitors, so do not delete anything on the strength of the list alone.
If you are working in the Webmin interface as well, our guides to checking a directory's size in Webmin's File Manager and enabling the HTML File Manager module cover two other everyday jobs.
References
Numbers in square brackets in the text link to these sources.
- Virtualmin: home page (opens in a new tab).Used for: the fact that Virtualmin is built on Webmin.
- Virtualmin documentation: Command Line API (opens in a new tab).Used for: running the command-line programs as root.
- Virtualmin documentation: list-domains (opens in a new tab).Used for: the options and what each one does.
- Virtualmin 8.3.0 source: list-domains.pl (opens in a new tab).Used for: filter behaviour, ordering, table width and password output.
- Virtualmin 8.3.0 source: create-domain.pl (opens in a new tab).Used for: why alias domains are not top-level.
- Virtualmin 8.3.0 source: commands-lib.pl (opens in a new tab).Used for: the –json and –xml options, and the remote API permission checks.
- Virtualmin 8.3.0 source: json-lib.pl (opens in a new tab).Used for: the structure of the JSON output.
- jq manual (opens in a new tab).Used for: how the @csv format quotes values.
- Virtualmin documentation: Remote API (opens in a new tab).Used for: json=1, multiline=1 and the documented administrator login.
- Apache HTTP Server documentation: httpd (opens in a new tab).Used for: what the -S option prints.
- nginx documentation: command-line parameters (opens in a new tab).Used for: the -T option that dumps the configuration.
- nginx documentation: server names (opens in a new tab).Used for: wildcard and regular expression names in server_name.
- BIND 9 manual: dig (opens in a new tab).Used for: the +short option.