Einleitung
Ich habe einen Windows Server 2025, auf dem eine Linux VM mit Docker-Containern läuft. Für das Deployment meiner statischen Photo-Gallery wollte ich eine sichere Lösung ohne viele offene Ports. Die Antwort: SSH-Tunneling - ein einzelner SSH-Port für alles.
Das Setup
- Windows Server 2025 mit öffentlicher IP
- Linux VM auf dem Windows Server (intern, keine öffentliche IP)
- Docker-Container mit nginx auf der VM
- Lokaler Rechner (Windows/Linux) für Deployment
Ziel
- Deployment via
rsynczur Linux VM - RDP-Zugriff zum Windows Server (ohne offenen RDP-Port)
- Zugriff auf Docker-Services (Portainer etc.)
- Alles über einen einzigen SSH-Port (22222)
Warum SSH-Tunneling?
Vorteile
✅ Ein Port für alles - Nur SSH Port 22222 nach außen offen
✅ Sicherer - Weniger Angriffsfläche als 5+ offene Ports
✅ Weniger Bot-Traffic - Non-Standard-Port drastisch weniger gescannt
✅ Flexibel - RDP, SSH, Portainer, MySQL etc. über Tunnel erreichbar
✅ Einfach - SSH-Key-Auth, keine VPN-Komplexität
Alternative: VPN
WireGuard wäre sicherer und professioneller, aber:
- ~30 Minuten Setup vs. 5 Minuten SSH
- Für ein privates Deployment-Setup ist SSH völlig ausreichend
Warum Port 22222 statt 22?
Nachteile eines öffentlichen SSH-Ports auf Port 22
- Tausende Bot-Angriffe pro Tag - Automatisierte Scanner suchen Port 22
- Log-Spam - Logs voll mit fehlgeschlagenen Login-Versuchen
- Server-Last - SSH muss ständig Anfragen abweisen
Port 22222 reduziert das drastisch
- ✅ 99% weniger Bot-Traffic - Scanner suchen nur Standard-Ports
- ✅ Saubere Logs - Echte Probleme fallen sofort auf
- ✅ Leicht zu merken - 5x die 2
Hinweis: Das ist "Security by Obscurity" - nicht technisch sicherer, aber praktisch viel angenehmer im Alltag.
Schritt 1: OpenSSH Server auf Windows Server 2025
Installation
# Als Administrator:
# Prüfen ob verfügbar:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
# Installieren:
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# Dienst aktivieren:
Set-Service -Name sshd -StartupType 'Automatic'
Start-Service sshd
# Prüfen:
Get-Service sshd
# Sollte: Status = Running
Konfiguration auf Port 22222
# Config bearbeiten:
notepad C:\ProgramData\ssh\sshd_config
# Zeile ändern (# entfernen!):
Port 22222
# Speichern und Dienst neu starten:
Restart-Service sshd
# Prüfen ob Port offen:
netstat -an | findstr "22222"
# Sollte zeigen: TCP 0.0.0.0:22222 ... LISTENING
Windows Firewall
# Firewall-Regel erstellen:
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP-22222" `
-DisplayName "OpenSSH Server (sshd) Port 22222" `
-Enabled True -Direction Inbound -Protocol TCP `
-Action Allow -LocalPort 22222
# Alte Port-22-Regel deaktivieren (optional):
Disable-NetFirewallRule -Name "OpenSSH-Server-In-TCP"
# Prüfen:
Get-NetFirewallRule -DisplayName "*22222*" | Format-List
Wichtig: Die Firewall-Regel ist sofort aktiv, kein Neustart nötig!
Cloud-Provider Firewall
In der Cloud-Console deines Providers:
- Server auswählen → Firewall/Security Groups
- Neue Regel: TCP Port 22222, Quelle: 0.0.0.0/0 (oder nur deine IP)
- Port 22 kann geschlossen bleiben
Ersten Test durchführen
# Von deinem lokalen Rechner:
Test-NetConnection -ComputerName SERVER-IP -Port 22222
# Sollte zeigen: TcpTestSucceeded : True
# Oder mit telnet/nc:
telnet SERVER-IP 22222
# oder
nc -zv SERVER-IP 22222
Schritt 2: SSH-Key-Authentifizierung
Auf dem lokalen Rechner
# Vorhandenen Key nutzen oder neuen erstellen:
ssh-keygen -t ed25519 -f C:\Users\USERNAME\.ssh\id_ed25519
# Bei "passphrase" ENTER drücken (kein Passwort für automatisches Deployment)
# Oder Passwort setzen und später mit ssh-agent verwalten
# Public Key anzeigen:
type C:\Users\USERNAME\.ssh\id_ed25519.pub
# Ausgabe kopieren (komplett!)
Linux/Mac:
# Key erstellen:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
# Public Key anzeigen:
cat ~/.ssh/id_ed25519.pub
Auf dem Windows Server
Per RDP verbinden und:
# Ordner erstellen (falls nicht vorhanden):
mkdir C:\ProgramData\ssh -ErrorAction SilentlyContinue
# Authorized Keys Datei bearbeiten:
notepad C:\ProgramData\ssh\administrators_authorized_keys
# Public Key einfügen (komplette Zeile)
# Speichern
# WICHTIG: Berechtigungen setzen
icacls C:\ProgramData\ssh\administrators_authorized_keys /inheritance:r
icacls C:\ProgramData\ssh\administrators_authorized_keys /grant "SYSTEM:(F)"
icacls C:\ProgramData\ssh\administrators_authorized_keys /grant "Administrators:(F)"
Warum diese Berechtigungen? OpenSSH auf Windows verweigert die Key-Auth, wenn andere Benutzer Zugriff auf die Datei haben. Die icacls-Befehle stellen sicher, dass nur SYSTEM und Administrators lesen können.
Testen
# Windows:
ssh -p 22222 Administrator@SERVER-IP
# Linux/Mac:
ssh -p 22222 Administrator@SERVER-IP
# Sollte OHNE Passwort einloggen! 🎉
Passwort-Authentifizierung deaktivieren (optional, aber empfohlen)
Auf dem Windows Server:
# sshd_config bearbeiten:
notepad C:\ProgramData\ssh\sshd_config
# Folgende Zeilen ändern/hinzufügen:
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
# Speichern und Dienst neu starten:
Restart-Service sshd
Schritt 3: SSH-Config für einfachen Zugriff
Windows
# Erstellen/Bearbeiten:
notepad C:\Users\USERNAME\.ssh\config
Linux/Mac
# Erstellen/Bearbeiten:
nano ~/.ssh/config
Config-Inhalt
# Windows Server mit Tunneln
Host myserver
HostName SERVER-IP-ADDRESS
Port 22222
User Administrator
IdentityFile ~/.ssh/id_ed25519
# RDP-Tunnel (Windows Server)
LocalForward 3389 localhost:3389
# SSH-Tunnel zur VM
LocalForward 2223 VM-INTERNAL-IP:22
# Portainer-Tunnel
LocalForward 9000 VM-INTERNAL-IP:9000
# Optional: MySQL
LocalForward 3306 VM-INTERNAL-IP:3306
# Linux VM über Tunnel
Host vm-docker
HostName localhost
Port 2223
User vm-username
# Erfordert aktiven Tunnel zu 'myserver'
VM-IP herausfinden:
Auf dem Windows Server:
# Hyper-V:
Get-VMNetworkAdapter -VMName <vm-name> | Select IPAddresses
# Docker:
docker inspect <container-name> | findstr IPAddress
# WSL2:
wsl hostname -I
Was ist LocalForward?
LocalForward erstellt einen Port auf deinem lokalen Rechner, der den Traffic durch den SSH-Tunnel zum Ziel leitet:
LocalForward 3389 localhost:3389
│ │ └─ Port auf Server
│ └─ Host vom Server aus gesehen
└─ Port auf lokalem Rechner
Bedeutet: localhost:3389 auf deinem Rechner → SSH-Tunnel → localhost:3389 auf dem Server (RDP)
Schritt 4: Tunnel nutzen
SSH zum Windows Server
# Tunnel starten (Terminal bleibt offen):
ssh myserver
# Jetzt laufen im Hintergrund:
# - RDP auf localhost:3389
# - VM-SSH auf localhost:2223
# - Portainer auf localhost:9000
Wichtig: Das Terminal muss offen bleiben, solange du die Tunnel nutzen willst!
RDP über Tunnel
# In neuem Terminal/Tab:
# Windows:
mstsc /v:localhost:3389
# Linux:
remmina -c rdp://localhost:3389
# Mac:
open rdp://localhost:3389
Verbindet zu Windows Server, OHNE offenen RDP-Port! 🔒
SSH zur VM
# In neuem Terminal/Tab:
ssh vm-docker
# Verbindet zur Linux VM über den Tunnel
Portainer/Services im Browser
http://localhost:9000
Tunnel prüfen
# Welche Ports sind lokal offen?
# Windows:
netstat -an | findstr "LISTENING"
# Linux/Mac:
netstat -tuln | grep LISTEN
# Sollte zeigen:
# 127.0.0.1:3389 (RDP)
# 127.0.0.1:2223 (VM-SSH)
# 127.0.0.1:9000 (Portainer)
Schritt 5: Deployment einrichten
VM vorbereiten
Auf der Linux VM:
# SSH-Key hinzufügen:
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
# Public Key einfügen
# Berechtigungen:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# nginx Volume-Mount-Pfad prüfen:
docker inspect nginx-container | grep Mounts -A 10
# z.B.: /var/www/html → /usr/share/nginx/html
Projekt-Config
In deinem Deployment-Projekt:
# projects/my-website/project.config
nano projects/my-website/project.config
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Deployment Configuration
#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Remote server hostname or SSH config alias
REMOTE_HOST="vm-docker"
# Remote username
REMOTE_USER="vm-username"
# Remote path where the site will be deployed
REMOTE_PATH="/var/www/html/my-website"
Deployment testen
# Terminal 1: Tunnel starten
ssh myserver
# Terminal 2: Deployment
cd ~/code/MyProject
./deploy.sh -p my-website
# 🎉 Deployment läuft über den Tunnel!
Troubleshooting: rsync nicht gefunden
Wenn rsync auf Windows fehlt:
# Git for Windows installieren (bringt rsync mit)
# Oder via WSL2:
wsl rsync -avz ...
# Oder Cygwin installieren
Workflow im Alltag
# 1. Tunnel starten (einmal am Tag):
ssh myserver
# 2. Arbeiten:
# - RDP: mstsc /v:localhost:3389
# - Portainer: http://localhost:9000
# - Deploy: ./deploy.sh -p my-website
# - VM-SSH: ssh vm-docker
# 3. Tunnel läuft solange Terminal offen bleibt
Optional: Persistent Tunnel
Für dauerhaften Tunnel ohne offenes Terminal:
Linux/Mac: autossh
# autossh installieren:
sudo apt install autossh # Debian/Ubuntu
brew install autossh # Mac
# Tunnel starten:
autossh -M 0 -f -N myserver
# -M 0: Kein Monitoring-Port
# -f: Im Hintergrund
# -N: Keine Shell
# Beenden:
pkill autossh
Windows: Task Scheduler
# tunnel-start.ps1 erstellen:
Start-Process ssh -ArgumentList "-N myserver" -WindowStyle Hidden
# Als geplante Aufgabe:
# Task Scheduler → Create Task
# Trigger: At logon
# Action: PowerShell -File C:\Path\tunnel-start.ps1
Windows: NSSM (Non-Sucking Service Manager)
# NSSM installieren (via Chocolatey):
choco install nssm
# SSH als Windows-Dienst:
nssm install SSHTunnel "C:\Windows\System32\OpenSSH\ssh.exe" "-N myserver"
nssm start SSHTunnel
# Dienst verwalten:
nssm stop SSHTunnel
nssm remove SSHTunnel
Systemd Service (Linux)
# ~/.config/systemd/user/ssh-tunnel.service
[Unit]
Description=SSH Tunnel to Server
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/ssh -N myserver
Restart=always
RestartSec=10
[Install]
WantedBy=default.target
# Aktivieren:
systemctl --user enable ssh-tunnel
systemctl --user start ssh-tunnel
# Status prüfen:
systemctl --user status ssh-tunnel
Sicherheit
Was haben wir erreicht?
✅ Nur ein Port offen: SSH Port 22222
✅ Key-only Auth: Keine Passwörter
✅ Non-Standard-Port: Drastisch weniger Bot-Scans
✅ Verschlüsselt: Alle Verbindungen über SSH-Tunnel
✅ Keine direkten Ports: RDP, Portainer etc. nur intern erreichbar
Weitere Härtung (optional)
# sshd_config auf dem Server:
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
fail2ban für zusätzlichen Schutz
Windows: IPBan (Open Source Alternative zu fail2ban)
# IPBan installieren:
# https://github.com/DigitalRuby/IPBan
# Blockiert automatisch IPs nach mehreren fehlgeschlagenen Login-Versuchen
Linux (auf der VM):
sudo apt install fail2ban
# Config für SSH:
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 22
maxretry = 3
bantime = 3600
SSH-Key mit Passwort schützen
Wenn du deinen SSH-Key mit Passwort schützen willst (empfohlen für zusätzliche Sicherheit):
# Key mit Passwort erstellen:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
# Passphrase eingeben
# SSH-Agent nutzen (speichert Passwort für Session):
# Windows:
Start-Service ssh-agent
Set-Service -Name ssh-agent -StartupType Automatic
ssh-add ~/.ssh/id_ed25519
# Passwort einmal eingeben
# Linux/Mac:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Passwort einmal eingeben
# Danach funktioniert SSH ohne Passwort-Abfrage!
Troubleshooting
Timeout bei SSH-Verbindung
# 1. SSH-Dienst läuft?
Get-Service sshd
# Sollte: Status = Running
# 2. Port prüfen:
netstat -an | findstr "22222"
# Sollte: TCP 0.0.0.0:22222 ... LISTENING
# 3. Windows Firewall prüfen:
Get-NetFirewallRule -DisplayName "*22222*"
# Sollte: Enabled = True
# 4. Cloud Firewall prüfen in Provider-Console
# 5. Von außen testen:
Test-NetConnection -ComputerName SERVER-IP -Port 22222
# Sollte: TcpTestSucceeded = True
Permission Denied (publickey)
# Public Key korrekt auf Server?
# Auf Server:
type C:\ProgramData\ssh\administrators_authorized_keys
# Berechtigungen korrekt?
icacls C:\ProgramData\ssh\administrators_authorized_keys
# Sollte nur SYSTEM und Administrators haben
# Key auf lokalem Rechner vorhanden?
# Lokal:
dir C:\Users\USERNAME\.ssh\
ssh-add -l # Zeigt geladene Keys
# SSH mit Debug-Output:
ssh -vvv -p 22222 Administrator@SERVER-IP
# Zeigt genau wo es scheitert
Tunnel funktioniert nicht
# SSH mit Debug-Output:
ssh -vvv myserver
# Port-Forwarding aktiv?
# Lokal:
netstat -an | findstr "3389" # RDP
netstat -an | findstr "2223" # VM-SSH
# VM-IP korrekt in SSH-Config?
# Auf Server testen:
ping VM-INTERNAL-IP
rsync: command not found
# Windows:
# Git for Windows installieren (bringt rsync mit)
# Oder WSL2 nutzen:
wsl rsync -avz ...
# Linux/Mac:
sudo apt install rsync # Debian/Ubuntu
brew install rsync # Mac
Connection timed out nach einiger Zeit
# SSH-Config erweitern:
notepad ~/.ssh/config
# Hinzufügen:
Host myserver
# ... bestehende Einstellungen ...
ServerAliveInterval 60
ServerAliveCountMax 3
# Hält Verbindung mit regelmäßigen "Heartbeats" aktiv
Monitoring und Logging
SSH-Logs auf Windows Server
# Event Viewer:
Get-EventLog -LogName Application -Source sshd -Newest 20
# Oder:
Get-WinEvent -LogName "OpenSSH/Operational" -MaxEvents 20
# Fehlgeschlagene Logins:
Get-WinEvent -LogName "OpenSSH/Operational" |
Where-Object {$_.Message -like "*Failed*"}
SSH-Logs auf Linux VM
# Live-Logs:
sudo tail -f /var/log/auth.log
# Fehlgeschlagene Logins:
sudo grep "Failed password" /var/log/auth.log
# Erfolgreiche Logins:
sudo grep "Accepted publickey" /var/log/auth.log
Tunnel-Status überwachen
# Skript: check-tunnel.sh
#!/bin/bash
if netstat -tuln | grep -q ":3389"; then
echo "✅ RDP-Tunnel läuft"
else
echo "❌ RDP-Tunnel nicht aktiv"
fi
if netstat -tuln | grep -q ":2223"; then
echo "✅ VM-SSH-Tunnel läuft"
else
echo "❌ VM-SSH-Tunnel nicht aktiv"
fi
Best Practices
1. Separate Keys für verschiedene Zwecke
# Persönlicher Key (mit Passwort):
~/.ssh/id_ed25519_personal
# Deployment Key (ohne Passwort):
~/.ssh/id_ed25519_deploy
# Server-Management Key:
~/.ssh/id_ed25519_server
2. SSH-Config strukturieren
# ~/.ssh/config
# === Production Servers ===
Host prod-*
User admin
IdentityFile ~/.ssh/id_ed25519_server
ServerAliveInterval 60
Host prod-web
HostName web.example.com
Port 22222
Host prod-db
HostName db.example.com
Port 22222
# === Development ===
Host dev-*
User developer
IdentityFile ~/.ssh/id_ed25519_dev
# === Deployment ===
Host deploy-*
User deployer
IdentityFile ~/.ssh/id_ed25519_deploy
3. Regelmäßige Security-Audits
# Checklist:
# ✅ Nur Key-Auth aktiviert?
# ✅ Root-Login deaktiviert?
# ✅ fail2ban/IPBan läuft?
# ✅ Logs regelmäßig prüfen?
# ✅ Unbenutzte Keys entfernen?
# ✅ Windows Updates installiert?
# ✅ SSH-Version aktuell?
4. Backup der SSH-Keys
# Keys verschlüsselt sichern:
tar czf ssh-keys-backup.tar.gz ~/.ssh/
gpg -c ssh-keys-backup.tar.gz
# Passwort eingeben
# In Cloud/USB speichern:
# ssh-keys-backup.tar.gz.gpg
Zusammenfassung
SSH-Tunneling ist eine elegante, sichere Lösung für Server-Management und Deployment:
Vorteile
- ✅ Einfach: Keine VPN-Komplexität
- ✅ Sicher: Minimale Angriffsfläche (nur ein Port)
- ✅ Flexibel: Beliebige Services tunneln
- ✅ Produktionsreif: Bewährt in vielen Projekten
- ✅ Kostenlos: Keine zusätzliche Software nötig
- ✅ Plattformübergreifend: Windows, Linux, Mac
Nachteile
- ⚠️ Terminal muss offen bleiben (außer bei Background-Lösungen)
- ⚠️ Bei Verbindungsabbruch muss neu verbunden werden
- ⚠️ Nicht ideal für viele gleichzeitige Nutzer
Wann VPN stattdessen?
- 🔐 Hochsensible Daten (Finanzen, Gesundheit)
- 👥 Mehrere Team-Mitglieder brauchen Zugriff
- 🏢 Firmen-Compliance verlangt VPN
- 🌐 Gesamtes Netzwerk soll erreichbar sein
- 🔄 24/7 Verbindung ohne manuelle Tunnel
Für Hobby-/Semi-Professional-Projekte ist SSH-Tunneling perfekt!
Ressourcen
Dokumentation
Tools
- PuTTY - SSH-Client für Windows (GUI)
- WinSCP - SCP/SFTP-Client für Windows
- Remmina - RDP-Client für Linux
- autossh - Persistent SSH-Tunnel
Sicherheit
- IPBan - fail2ban für Windows
- SSH Security Best Practices
Tags: #SSH #Windows-Server #Deployment #DevOps #Security #Tunneling #Docker #RDP
Hinweis: Diese Anleitung basiert auf echten Produktions-Erfahrungen mit einem Windows Server 2025 und Linux VM Setup. Alle Befehle wurden getestet und funktionieren.