Percio Castelo.
PT Resume
Investigating phantom emails, hidden ports, and traffic redirection in VestaCP
← Back to blog

Investigating phantom emails, hidden ports, and traffic redirection in VestaCP

10/31/2026 · 5 min · Servers · 906 views

Anyone inheriting or maintaining a server running VestaCP quickly discovers a mix of speed, simplicity, and architectural surprises. The web interface is clean and lightweight, but beneath the hood, its filesystem organization and daemon bindings follow specific rules that can baffle even seasoned Linux administrators.

Consider a common operational situation: an email account reporting nearly half a gigabyte of used disk space that nowhere appears where you would normally expect it, an administrative dashboard refusing to load on standard ports, and a network gateway silently dropping inbound traffic.

Here is the step-by-step diagnostic breakdown that resolved this operational puzzle, covering Maildir filesystem mapping, socket auditing, and reverse SSH tunneling to restore access.


1. Where does VestaCP actually store email messages?#

Coming from other control panels, the initial reflex is to browse into the domain directory expecting to find mail folders directly inside the web document root.

Inside VestaCP, inspecting the user web root yields:

[root@server ~]# cd /home/admin/web/mydomain.com/
[root@server mydomain.com]# ls -la
cgi-bin/            logs/               private/            public_shtml/       stats/
document_errors/    mail.zip            public_html/        restore_errors.log

No mailboxes exist here. In VestaCP, /web/ is strictly isolated for web servers (Apache and Nginx) serving website content.

The panel enforces a clear division between configuration metadata and raw message content.

Configuration files vs. actual message data#

Checking the domain's mail configuration directory:

[root@server ~]# ls /home/admin/conf/mail/mydomain.com/
aliases    antispam   antivirus  dkim.pem   fwd_only   passwd

Here lie the control definitions for Exim and Dovecot: address aliases, DKIM keys (dkim.pem), antispam rules, and the passwd file containing account password hashes. Where, then, are the physical emails consuming storage space?

To manage disk quotas and enforce operating system file permissions properly, raw message data stored in the Maildir format is kept entirely outside /conf/ and /web/.

The canonical mail storage path in VestaCP is:

/home/USER/mail/DOMAIN/ACCOUNT/Maildir/

Confirming real disk usage through the terminal with du:

[root@server ~]# ls -F /home/admin/mail/mydomain.com/
billing/  contact/  finance/  support/

[root@server ~]# du -sh /home/admin/mail/mydomain.com/*
259M    /home/admin/mail/mydomain.com/billing
434M    /home/admin/mail/mydomain.com/contact
1.0M    /home/admin/mail/mydomain.com/support

Inside any of these account directories, Dovecot's standard layout is revealed:

[root@server ~]# ls -la /home/admin/mail/mydomain.com/contact/Maildir/
drwx------. 5 admin mail 4096 May 12 14:22 .
drwx------. 3 admin mail 4096 May 12 14:22 ..
drwx------. 2 admin mail 4096 May 12 14:22 cur
drwx------. 2 admin mail 4096 May 12 14:22 new
drwx------. 2 admin mail 4096 May 12 14:22 tmp
drwx------. 2 admin mail 4096 May 12 14:22 .Sent
drwx------. 2 admin mail 4096 May 12 14:22 .Trash
  • new/: Fresh, unread messages recently processed by the MTA.
  • cur/: Read and processed messages.
  • .Sent / .Trash: Subfolders created by IMAP clients (stored as hidden dot-directories).

2. The hidden port mystery and gateway NAT blocks#

Another frequent puzzle occurs when the admin dashboard fails to respond in web browsers. By default, VestaCP listens on port 8083. However, browsing to https://server-ip:8083 timed out.

Auditing active listening sockets with ss uncovered the reality:

[root@server ~]# ss -tulnp
Netid State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port
tcp   LISTEN 0      20      *:25                *:* users:(("exim",pid=1755,fd=3))
tcp   LISTEN 0      128     192.168.207.103:443 *:* users:(("httpd",pid=1954,fd=4))
tcp   LISTEN 0      128     *:2087              *:* users:(("vesta-nginx",pid=1889,fd=6))
tcp   LISTEN 0      50      *:3306              *:* users:(("mysqld",pid=2333,fd=15))
tcp   LISTEN 0      128     *:22                *:* users:(("sshd",pid=1738,fd=3))

The findings were straightforward:

  1. The vesta-nginx process (the control panel's dedicated web server) was not listening on port 8083. It had been customized to bind to port 2087.
  2. The server's network adapter had a purely private IP address (192.168.207.103), situated behind an edge NAT router.

Testing connectivity from an external machine on the internet:

user@remote:~$ telnet 203.0.113.86 2087
Trying 203.0.113.86...
^C

The handshake stalled indefinitely. However, testing locally from inside the server itself:

[root@server ~]# telnet 127.0.0.1 2087
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

The service was running smoothly. The issue stemmed from the infrastructure gateway router: while ports 80, 443, and 22 were correctly forwarded, traffic directed toward port 2087 was dropped by default.


3. Piercing network blocks with a reverse SSH tunnel#

When immediate dashboard access is required and you lack administrative credentials to modify edge firewall port forwardings, an encrypted bridge via Reverse SSH Tunneling is the cleanest solution.

Why local forwarding with -L failed#

Attempting to establish a local forwarding from inside the host toward its own public IP address failed:

[root@server ~]# ssh -L 8083:127.0.0.1:2087 [email protected]
ssh: connect to host 203.0.113.86 port 22: No route to host

The kernel rejects the packets because the perimeter router lacks NAT Hairpinning support.

The reverse bridge solution (ssh -R)#

Instead, traffic is routed through an external intermediary server with a public IP address (represented here by gateway.example.com at 198.51.100.170).

From inside the firewalled VestaCP server, initiate the reverse connection:

[root@server ~]# ssh -R 8888:127.0.0.1:2087 [email protected]

This instructs the intermediary server to open port 8888 and route any traffic arriving on that port through the encrypted tunnel back to the internal 127.0.0.1:2087 socket on the VestaCP server.

Enabling external access on the intermediary host#

By default, OpenSSH restricts ports opened by -R to the remote loopback interface (127.0.0.1). To allow connections from your local desktop workstation to https://gateway.example.com:8888, enable global listening on the intermediary server:

  1. Edit the SSH daemon configuration on the intermediary server:
root@gateway:~# nano /etc/ssh/sshd_config
  1. Add or update the directive:
GatewayPorts yes
  1. Restart the SSH service:
root@gateway:~# systemctl restart ssh
  1. Allow the selected port through the local firewall:
root@gateway:~# ufw allow 8888/tcp
# Or via raw iptables:
root@gateway:~# iptables -I INPUT -p tcp --dport 8888 -j ACCEPT
  1. Reconnect the tunnel from the VestaCP server, explicitly binding to all interfaces (0.0.0.0):
[root@server ~]# ssh -R 0.0.0.0:8888:127.0.0.1:2087 [email protected]

Now, navigating to https://gateway.example.com:8888 in your browser securely routes packets across port 22 directly into VestaCP's internal web panel.


4. Debugging Nginx syntax errors in VestaCP#

While adjusting port assignments inside /usr/local/vesta/nginx/conf/nginx.conf, a syntax mistake caused the dashboard service to fail on restart:

[root@server ~]# systemctl status vesta.service
May 12 16:59:56 vesta[23516]: Starting vesta-nginx: nginx: [emerg] invalid parameter "server_name" in /usr/local/vesta/nginx/conf/nginx.conf:83
May 12 16:59:56 vesta[23516]: [FAILED]

Diagnosing the break#

Nginx requires strict statement termination. Omitting a semicolon (;) on any directive causes the parser to treat the next line's keyword as an invalid parameter of the preceding statement, producing errors like invalid parameter "server_name".

Editing the file to restore clean block structure:

[root@server ~]# vi +83 /usr/local/vesta/nginx/conf/nginx.conf

The block was corrected with proper directive endings:

    # Vhost
    server {
        listen          2087;
        server_name     _;
        root            /usr/local/vesta/web;
        charset         utf-8;

Best practice: test syntax before restarting#

To avoid service downtime in production, always test configuration changes using the -t flag with the specific vesta-nginx binary:

[root@server ~]# /usr/local/vesta/nginx/sbin/nginx -t -c /usr/local/vesta/nginx/conf/nginx.conf
nginx: the configuration file /usr/local/vesta/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/vesta/nginx/conf/nginx.conf test is successful

Only restart the daemon after verifying that the test passes:

[root@server ~]# systemctl start vesta.service

If changing ports triggers:

nginx: [emerg] bind() to 0.0.0.0:443 failed (98: Address already in use)

Run ss -tulnp | grep :443 to determine which web server (Apache or standard Nginx) is holding that socket.


5. Where is the default web root for raw IP requests?#

In standard Nginx setups, direct IP hits (http://203.0.113.86/) land on /usr/share/nginx/html/.

In VestaCP, requests lacking a domain host header route automatically to the VirtualHost marked as default_server.

That root directory is located at:

/home/admin/web/default/public_html/

To customize the default landing page displayed when accessing the bare server IP, place your files inside this specific directory.


Operational survival guide for VestaCP troubleshooting#

Keep these operational insights handy whenever maintaining a VestaCP host:

  1. Email storage lives in /home/USER/mail/: The /web/ folder is strictly for websites; /conf/ houses only Exim/Dovecot credentials and rules.
  2. Audit listening sockets with ss -tulnp: Checking sockets directly confirms whether the panel was reassigned from 8083 to a custom port like 2087.
  3. Use reverse SSH tunnels for NAT bypass: Combining ssh -R with GatewayPorts yes provides immediate, encrypted access past restrictive perimeter firewalls.
  4. Validate Nginx configurations with nginx -t -c: Always verify syntax before restarting daemons to eliminate accidental syntax-related outages.
  5. Raw IP web requests resolve to default/public_html: This directory controls the default landing page when no domain is provided.

Understanding VestaCP's distinct architectural layers turns troubleshooting from guesswork into a structured, reliable process.

Was this article helpful?

Leave a quick reaction to help prioritize future technical guides:

906 reads
Creative Commons Attribution-NonCommercial

This post is licensed under CC BY-NC.

Comments

Join the discussion below.

0 comments