Back to tutorials
Tutorial

SSH Port Forwarding Tutorial (2026): Securely Reach Server Admin Panels, SMTP, and Databases on a VPS Without Public Ports

SSH port forwarding tutorial for 2026: access admin panels, SMTP, and databases on your VPS securely without opening extra firewall ports.

By Anurag Singh
Updated on Jul 01, 2026
Category: Tutorial
Share article
SSH Port Forwarding Tutorial (2026): Securely Reach Server Admin Panels, SMTP, and Databases on a VPS Without Public Ports

You don’t need to publish phpMyAdmin, Redis, cPanel ports, or a private app dashboard to manage them. Use an SSH tunnel instead.

An SSH tunnel gives you encrypted, logged access over port 22 (or your custom SSH port). Everything else stays closed. This SSH port forwarding tutorial focuses on the hosting-style tunnels you’ll actually use on a VPS or dedicated server.

The examples assume Ubuntu 24.04/26.04-style layouts. The commands still apply to Debian and most Linux distributions.

You’ll set up local, remote, and dynamic (SOCKS) forwarding. Then you’ll fix the common “it connects but nothing loads” cases.

What you’ll use SSH port forwarding for in real hosting work

  • Access a database (MySQL/PostgreSQL) bound to 127.0.0.1 without opening 3306/5432 to the internet.
  • Reach an internal admin panel (Grafana, Netdata, app dashboard) that listens only on localhost.
  • Safely test SMTP on a mail server without exposing submission ports during setup.
  • Temporarily access a staging site or internal vhost before DNS cutover.

If you’re building a new hosting node, pair tunnels with a tight network policy. A solid baseline is simple.

Make SSH reachable only from your IPs. Open web ports as needed. Keep everything else closed.

If you haven’t hardened that yet, start with our UFW firewall configuration tutorial.

Prerequisites (keep these tight)

  • A VPS or dedicated server you can SSH into.
  • An SSH user with sudo for the server tasks (not required for tunneling itself).
  • SSH keys set up (recommended), and password login disabled where possible.

If you still use password-based SSH, fix that first. This workflow gets you to keys + sane defaults quickly: SSH key setup tutorial.

Quick diagnostic: confirm the service is truly private

Before you tunnel anything, confirm what the service is listening on. On the server, run:

sudo ss -lntp | head -n 50

Look for output like:

LISTEN 0 4096 127.0.0.1:3306   0.0.0.0:*   users:(("mysqld",pid=...,fd=...))
LISTEN 0 4096 127.0.0.1:8080   0.0.0.0:*   users:(("myapp",pid=...,fd=...))

127.0.0.1:PORT (or ::1:PORT) means “localhost only.” That’s exactly what you want for admin-only services.

If you see 0.0.0.0:PORT, the service is reachable on the network interface. Treat that as a fire drill.

Review firewall rules and bind settings.

Local port forwarding (the one you’ll use most)

Local forwarding maps a port on your laptop to a port reachable from the server side of the SSH session.

You connect to localhost on your machine. SSH carries the traffic to the server.

Example A: tunnel MySQL (3306) safely

On your computer:

ssh -N -L 13306:127.0.0.1:3306 admin@SERVER_IP
  • -L 13306:127.0.0.1:3306 binds local port 13306 to the server’s 127.0.0.1:3306.
  • -N

Then point your local MySQL client at:

Host: 127.0.0.1
Port: 13306

This pattern holds up on shared hosting stacks too. Keep database ports private, and use SSH for admin reach.

If you’re sizing infrastructure for multiple sites, a HostMyCode VPS gives you the network control that makes this approach practical.

Example B: tunnel an internal admin panel (localhost:8080)

On your computer:

ssh -N -L 18080:127.0.0.1:8080 admin@SERVER_IP

Then open:

http://127.0.0.1:18080/

If the app cares about the Host header, you may need a local hosts entry or a small reverse proxy on your machine.

Don’t guess. Hit it with curl first and confirm the response.

Make it harder to mess up: use ExitOnForwardFailure

SSH can successfully log in even when the port forward fails. This often happens when the local port is already in use.

It can also fail when the forward can’t bind for another reason. Add:

ssh -N \
  -o ExitOnForwardFailure=yes \
  -L 18080:127.0.0.1:8080 \
  admin@SERVER_IP

Now the session exits immediately if it can’t bind the forward. That one option saves real time during incident work.

Remote port forwarding (useful for temporary inbound access)

Remote forwarding flips the direction. You open a port on the server that forwards back to your local machine.

This is useful for quick demos. It also helps when you need controlled inbound access and you’re stuck behind NAT.

On your computer, to expose your local 3000 to the server as 127.0.0.1:13000:

ssh -N -R 13000:127.0.0.1:3000 admin@SERVER_IP

On the server, test it:

curl -I http://127.0.0.1:13000/

Security note: by default, OpenSSH binds remote forwards to localhost on the server. That’s the safe default.

Don’t bind remote forwards to 0.0.0.0 unless you have a specific, temporary reason. Think through who can reach that port before you do it.

Dynamic port forwarding (SOCKS proxy) for “browse like the server” checks

Dynamic forwarding creates a local SOCKS proxy. Tools that use it send traffic through the SSH connection.

The server becomes your egress point.

This is useful for:

  • Testing how a site looks from the server’s network location.
  • Reaching multiple internal services without creating multiple -L forwards.
  • Quick triage when you suspect geo/IP-based blocks.

Create the SOCKS proxy on your computer:

ssh -N -D 1080 admin@SERVER_IP

Set your browser proxy to SOCKS5 host 127.0.0.1, port 1080.

For command-line checks with curl:

curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

If you need an internal-only web app with SSL termination and predictable routing (instead of a browser proxy), put a reverse proxy in front of it.

Our reverse proxy setup guide shows a hosting-friendly Nginx front-end that works well alongside control panels.

Persist tunnels safely: SSH config + systemd user service

Long SSH commands are easy to mistype. For tunnels you use repeatedly, put host settings in ~/.ssh/config on your computer.

Step 1: create an SSH config host entry

nano ~/.ssh/config

Example:

Host hmc-prod-vps
  HostName SERVER_IP
  User admin
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ed25519
  ServerAliveInterval 30
  ServerAliveCountMax 3
  ExitOnForwardFailure yes

Step 2: run tunnels with short commands

ssh -N -L 13306:127.0.0.1:3306 hmc-prod-vps

For a more durable setup (especially if you keep a DB tunnel up all day), use a user-level systemd unit on Linux.

macOS alternatives may vary.

On a Linux workstation:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/ssh-tunnel-mysql.service

Service file:

[Unit]
Description=SSH tunnel to production MySQL (localhost:13306)
After=network-online.target

[Service]
ExecStart=/usr/bin/ssh -N -L 13306:127.0.0.1:3306 hmc-prod-vps
Restart=always
RestartSec=3

[Install]
WantedBy=default.target

Enable it:

systemctl --user daemon-reload
systemctl --user enable --now ssh-tunnel-mysql.service
systemctl --user status ssh-tunnel-mysql.service

The tunnel stays up and reconnects after Wi‑Fi drops.

Hardening the SSH side (so tunnels don’t become your weak spot)

Port forwarding only helps if your SSH access is tight. Lock down the server side before you start depending on tunnels.

1) Limit who can log in

Edit:

sudo nano /etc/ssh/sshd_config

Useful directives for hosting servers:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers admin deploy

Reload SSH safely:

sudo sshd -t && sudo systemctl reload ssh

2) Decide whether you want to allow forwarding at all

On a multi-user box (resellers, shared environments, teams), you may want to restrict forwarding.

Don’t leave it wide open by default.

Options (choose intentionally):

  • Allow all forwarding (common on single-tenant VPS): AllowTcpForwarding yes
  • Disable forwarding (strict): AllowTcpForwarding no
  • Limit by user using Match blocks: allow forwarding only for admins

Example Match block:

AllowTcpForwarding no

Match User admin
  AllowTcpForwarding yes

If you run cPanel/WHM for client hosting, apply the same “minimum access” thinking to the panel. Keep this in your runbook: cPanel hardening tutorial.

Common tunnel failures and fast fixes (real troubleshooting)

Problem: “bind: Address already in use”

Your local port is already taken. Choose another local port (like 13306 instead of 3306), or find what’s listening:

sudo lsof -iTCP:13306 -sTCP:LISTEN

Problem: SSH connects, but the website/admin panel doesn’t load

This usually comes down to one of these:

  • You forwarded the wrong destination (e.g., the app listens on 127.0.0.1:8000, not :8080).
  • The app listens on IPv6 only (::1). Try forwarding to localhost instead of 127.0.0.1.
  • Firewall blocks the SSH port (or your IP). Fix the firewall rule first.

Confirm the binding on the server:

sudo ss -lntp | grep -E '(:8080|:8000|:3000)'

Problem: “channel X: open failed: connect failed: Connection refused”

SSH can’t reach the destination host:port from the server side. The service is down, or it’s listening somewhere else.

Restart the service or correct the forwarded destination.

Problem: tunnels drop after a few minutes

Add keepalives on the client (in ~/.ssh/config):

ServerAliveInterval 30
ServerAliveCountMax 3

If your server is getting hammered (brute-force noise) and SSH is flapping, investigate rather than masking it. This guide pairs well here: SSH brute-force troubleshooting tutorial.

Practical hosting pattern: keep services private, expose only web and mail

For a typical VPS hosting stack in 2026, this layout stays boring—in a good way:

  • Public: 80/443 to the web server or reverse proxy.
  • Public (mail, if you host mail): 25/465/587/143/993 as needed.
  • Private: databases, Redis, internal admin UIs, monitoring dashboards.
  • Admin access: SSH from restricted IPs, plus tunnels for everything else.

If you care about mail deliverability, treat DNS and rDNS as part of the setup. A tunnel won’t fix missing authentication.

For the mail side, see our email authentication setup tutorial.

Checklist: your “safe tunnel” standard operating procedure

  • Confirm the destination service listens on 127.0.0.1 or a private interface.
  • Use ExitOnForwardFailure=yes so you don’t trust a broken tunnel.
  • Use SSH keys; disable SSH passwords where feasible.
  • Restrict SSH at the firewall (source IP allowlist) if your workflow permits it.
  • Prefer local forwards for admin access; keep remote forwards rare and temporary.
  • Document tunnels in ~/.ssh/config for repeatability.

Summary: use tunnels to reduce exposed ports without losing control

SSH tunnels let you shrink your server’s exposed surface area without making admin work painful.

Instead of opening ports “just for a minute,” keep services private. Bring them to your machine only when needed.

If you want a predictable environment for secure tunneling—static IP options, clean Linux images, and room to grow—start with a managed VPS hosting plan or deploy your own HostMyCode VPS. You get the control tunnels depend on, without shared-host constraints.

If you’re tightening network access on a production site, SSH tunnels are easiest to run on infrastructure you control. HostMyCode offers VPS hosting for hands-on admins and managed VPS hosting if you’d rather hand off patching and baseline hardening while you focus on your sites.

FAQ

Is SSH port forwarding safe enough for production admin access?

Yes, as long as SSH is hardened (keys, limited users, restricted source IPs where possible). In practice, tunnels are usually safer than leaving admin ports publicly reachable.

Should I forward to 127.0.0.1 or localhost?

Use 127.0.0.1 when the service listens on IPv4 localhost. Use localhost if the service binds to IPv6 only (::1) or you’re unsure and want name resolution to pick the right stack.

Can I use SSH tunnels with cPanel/WHM?

Yes. You can tunnel WHM or Webmail access in a pinch, but the better default is restricting panel access at the firewall and IP allowlisting in WHM, then tunneling only internal services.

Why not just open the database port and whitelist my IP?

You can, but it tends to drift. IPs change, rules get left behind, and scanners still hit the port. Keeping the database private and tunneling avoids that whole class of problems.

What’s the simplest way to keep tunnels running?

Put stable hosts in ~/.ssh/config and run tunnel-only sessions with -N. For always-on developer workflows, use a systemd user service to auto-reconnect.