Plink (PuTTY Link)
Plink is PuTTY’s command-line SSH client for non-interactive remote command execution, port forwarding, tunneling, and automation across Windows, Linux, and macOS.
Installation
Windows
# Chocolatey
choco install putty
# Scoop
scoop install putty
# Manual download
# https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html
# Extract plink.exe and add to PATH
Linux
# Ubuntu/Debian
sudo apt-get install putty-tools
# RHEL/CentOS/Fedora
sudo dnf install putty
# Arch
sudo pacman -S putty
# Build from source
git clone https://git.tartarus.org/simon/putty.git
cd putty
mkdir build && cd build
cmake ..
make && sudo make install
macOS
# Homebrew
brew install putty
# Build from source (requires Xcode)
git clone https://git.tartarus.org/simon/putty.git
cd putty/unix
./configure && make && sudo make install
Basic SSH Commands
Simple Remote Execution
# Execute single command on remote server
plink user@example.com ls -la /home/user
# Execute command with password (not recommended)
plink -pw password user@example.com whoami
# Execute multiple commands
plink user@example.com "cd /tmp && pwd && ls -la"
# Get command exit code
plink user@example.com "test -f /etc/shadow && echo 'exists' || echo 'not found'"
echo "Exit code: $?"
Interactive vs Non-Interactive
# Non-interactive (returns immediately after command)
plink -batch user@example.com "grep pattern /var/log/syslog"
# Suppress banner and prompts
plink -batch -v0 user@example.com "uptime"
# Disable SSH protocol warnings
plink -sshver 2 user@example.com "id"
Connection Options
# Specify SSH port
plink -P 2222 user@example.com "uname -a"
# Specify private key file
plink -i ~/.ssh/id_rsa user@example.com "hostname"
# Use stored PuTTY session
plink "My Server" "date"
# Connection timeout (seconds)
plink -connectionattempts 3 user@example.com "echo connected"
# Verbosity levels (0=silent, 1=fatal, 2=error, 3=warning, 4=info)
plink -v2 user@example.com "uptime"
Key-Based Authentication
Using SSH Keys
# Convert OpenSSH key to PuTTY format
puttygen id_rsa -o -O private-sshcom-3des-cbc -o private.ppk
# Use PuTTY private key (.ppk)
plink -i C:\Users\user\.ssh\private.ppk user@example.com "pwd"
# Auto-add key to agent
plink -A user@example.com "ssh internal-server 'uname -a'"
# Load key from ssh-agent
plink -agent user@example.com "whoami"
SSH Agent Setup
# Start Pageant (PuTTY SSH agent) with key
pageant.exe C:\Users\user\.ssh\private.ppk
# Use agent-forwarded connection
plink -A user@example.com "ssh internal-host 'id'"
# Verify agent has keys
plink -agent user@example.com "ssh-add -l"
Port Forwarding and Tunneling
Local Port Forwarding
# Forward local port to remote service
plink -L 3306:localhost:3306 user@example.com -N
# Forward localhost:5432 to database server through bastion
plink -L 5432:db.internal:5432 bastion.example.com -N
# Multiple forwards in one connection
plink -L 8080:webserver:80 -L 3306:dbserver:3306 user@example.com -N
# Bind to specific interface
plink -L 127.0.0.1:9200:elasticsearch:9200 user@example.com -N
# Keep connection open indefinitely
plink -N -once user@example.com cat
Remote Port Forwarding
# Expose local port to remote host
plink -R 8080:localhost:3000 user@example.com -N
# Allow remote connections to forwarded port
plink -R 0.0.0.0:8080:localhost:3000 user@example.com -N
# Multiple remote forwards
plink -R 8080:localhost:3000 -R 9000:localhost:5000 user@example.com -N
Dynamic Port Forwarding (SOCKS Proxy)
# Create SOCKS proxy through SSH
plink -D 1080 user@example.com -N
# Use with curl via SOCKS proxy
curl -x socks5://localhost:1080 http://internal.example.com
# Use with Firefox or other apps (configure proxy to localhost:1080)
Batch and Script Automation
Simple Script Execution
#!/bin/bash
# Execute commands on multiple servers
SERVERS=(
"server1.example.com"
"server2.example.com"
"server3.example.com"
)
KEY="~/.ssh/id_rsa"
USER="admin"
for server in "${SERVERS[@]}"; do
echo "=== $server ==="
plink -i "$KEY" -P 22 "$USER@$server" "uptime"
done
Remote Script Execution
#!/bin/bash
# Run local script on remote server
plink user@example.com bash < local_script.sh
# With arguments
plink user@example.com "bash -s argument1 argument2" < local_script.sh
# Run script from stdin
echo '#!/bin/bash
echo "Hostname: $(hostname)"
echo "Uptime: $(uptime)"
free -h' | plink user@example.com /bin/bash
Piping Between Commands
#!/bin/bash
# Complex piping through SSH
plink user@example.com "cat /var/log/auth.log | grep 'Failed' | wc -l"
# Compress and transfer
tar czf - /important/data | plink user@example.com "cat > /tmp/backup.tar.gz"
# Stream processing
plink user@example.com "tail -f /var/log/syslog" | grep "ERROR"
Conditional Execution
#!/bin/bash
# Exit status handling
if plink -batch user@example.com "test -f /etc/config"; then
echo "Config file exists"
plink user@example.com "cat /etc/config"
else
echo "Config file not found"
plink user@example.com "ls -la /etc/"
fi
# Check exit code
plink user@example.com "exit 42"
EXIT_CODE=$?
echo "Remote exit code: $EXIT_CODE"
Advanced Tunneling Scenarios
Database Access Through Bastion
#!/bin/bash
# Create tunnel to MySQL through bastion host
BASTION="bastion.example.com"
DB_HOST="db.internal"
DB_PORT="3306"
LOCAL_PORT="3306"
USER="sysadmin"
# Create port forward in background
plink -i ~/.ssh/bastion.ppk \
-L $LOCAL_PORT:$DB_HOST:$DB_PORT \
$USER@$BASTION -N &
TUNNEL_PID=$!
sleep 2
# Connect to database through tunnel
mysql -h 127.0.0.1 -P $LOCAL_PORT -u dbuser -p
# Clean up tunnel
kill $TUNNEL_PID
Multi-Hop Tunneling
#!/bin/bash
# Chain tunnels: Local -> Jump Host -> Internal Server -> Database
JUMP="jump.example.com"
INTERNAL="internal.example.com"
DB="database.internal"
# Create tunnel to internal server through jump host
plink -i ~/.ssh/id_rsa \
-L 2222:$INTERNAL:22 \
user@$JUMP -N &
sleep 1
# Create tunnel to database through internal server
plink -i ~/.ssh/id_rsa \
-P 2222 \
-L 3306:$DB:3306 \
user@localhost -N &
sleep 1
# Now connect to database at localhost:3306
mysql -h 127.0.0.1
Reverse Shell Tunnel
#!/bin/bash
# Create reverse tunnel for accessing local service from remote
LOCAL_PORT="8000"
REMOTE_PORT="9000"
REMOTE_HOST="user@example.com"
plink -i ~/.ssh/id_rsa \
-R $REMOTE_PORT:localhost:$LOCAL_PORT \
$REMOTE_HOST -N
# Remote user can access: localhost:9000 -> local:8000
File Transfer Integration
SCP via Plink
# While plink doesn't do SCP, use pscp (PuTTY SCP)
# Download file from remote
pscp -i ~/.ssh/id_rsa user@example.com:/tmp/file.txt .
# Upload file to remote
pscp -i ~/.ssh/id_rsa file.txt user@example.com:/tmp/
# Recursive directory copy
pscp -i ~/.ssh/id_rsa -r user@example.com:/home/user/data .
Sync Through Tunnel
#!/bin/bash
# Create rsync tunnel and sync files
HOST="backup.example.com"
USER="backupuser"
REMOTE_PATH="/backup/data"
LOCAL_PATH="./local_backup"
# Create tunnel
plink -i ~/.ssh/id_rsa \
-L 2222:localhost:22 \
user@$HOST -N &
TUNNEL_PID=$!
sleep 1
# Rsync through tunnel
rsync -av -e "ssh -p 2222" $USER@localhost:$REMOTE_PATH $LOCAL_PATH
kill $TUNNEL_PID
Scripting Examples
System Monitoring Script
#!/bin/bash
# Monitor multiple servers via plink
SERVERS="server1 server2 server3"
KEY="~/.ssh/id_rsa"
USER="monitor"
for server in $SERVERS; do
echo "=== $server at $(date) ==="
plink -i "$KEY" -batch "$USER@$server" << 'EOF'
echo "CPU: $(grep 'cpu ' /proc/stat | head -1)"
echo "Memory: $(free -h | grep '^Mem')"
echo "Disk: $(df -h / | tail -1)"
echo "Load: $(uptime | awk -F 'load average:' '{print $NF}')"
EOF
echo ""
done
Backup Automation
#!/bin/bash
# Automated backup script using plink tunnels
BACKUP_SERVER="backup.example.com"
BACKUP_USER="backupuser"
DB_PORT="3306"
DB_USER="dbuser"
DB_PASS="dbpass"
DB_NAME="production"
BACKUP_DIR="/backups"
# Create tunnel to database
plink -i ~/.ssh/id_rsa \
-L 3306:db.internal:3306 \
$BACKUP_USER@$BACKUP_SERVER -N &
TUNNEL_PID=$!
sleep 2
# Create backup through tunnel
mysqldump -h 127.0.0.1 \
-u $DB_USER -p$DB_PASS \
$DB_NAME | gzip > ${BACKUP_DIR}/db_$(date +%Y%m%d).sql.gz
# Upload backup to server
pscp -i ~/.ssh/id_rsa \
${BACKUP_DIR}/db_$(date +%Y%m%d).sql.gz \
$BACKUP_USER@$BACKUP_SERVER:$BACKUP_DIR/
kill $TUNNEL_PID
Cron Job Example
#!/bin/bash
# Plink in cron: fetch remote logs and analyze
USER="sysadmin"
HOST="production.example.com"
KEY="/home/monitor/.ssh/id_rsa"
# Run maintenance task daily at 2 AM
# 0 2 * * * /usr/local/bin/remote_maintenance.sh
plink -i "$KEY" -batch "$USER@$HOST" << 'EOF'
# Clean old logs
find /var/log -mtime +30 -delete
# Archive system logs
tar czf /backups/syslog_$(date +\%Y\%m\%d).tar.gz /var/log/syslog*
# Report disk usage
echo "Disk Usage Report: $(date)" >> /var/log/maintenance.log
df -h >> /var/log/maintenance.log
EOF
Troubleshooting
Common Issues
Issue: “Access denied” or “Disconnected: No supported authentication methods”
# Verify key permissions (should be 600)
ls -la ~/.ssh/id_rsa
# Test SSH connection with verbose output
plink -v user@example.com "id"
# Check if key is loaded in Pageant
pageant -l
# Try password authentication
plink -pw password user@example.com "whoami"
Issue: “Server unexpectedly closed network connection”
# Use batch mode to suppress interactive prompts
plink -batch user@example.com "uptime"
# Increase connection timeout
plink -connectionattempts 5 user@example.com "uptime"
# Check SSH server logs on remote host
ssh user@example.com "tail -f /var/log/auth.log"
Issue: Port forwarding not working
# Verify tunnel is listening
netstat -tlnp | grep 3306
# Test connectivity to tunnel
telnet localhost 3306
# Check remote port is accessible
plink user@example.com "netstat -tlnp | grep 3306"
# Keep tunnel open with -N flag
plink -N user@example.com -L 3306:localhost:3306
Issue: “putty not found” on Linux/macOS
# PuTTY tools are in putty-tools package
sudo apt-get install putty-tools
# Verify plink is in PATH
which plink
plink --version
Issue: Timeout on slow connections
# Increase timeout value (milliseconds)
plink -sessiontimeout 60 user@example.com "slow command"
# Keep connection alive with keepalive
plink -sshver 2 user@example.com -N
# Use with longer-running operations
plink user@example.com "timeout 300 long_running_job"
Security Best Practices
- Convert OpenSSH keys to PuTTY format (.ppk) with
puttygen - Use
-batchflag to prevent interactive prompts in scripts - Store private keys securely with restricted permissions (600)
- Use Pageant (SSH agent) to manage multiple keys
- Avoid embedding passwords; use key-based authentication
- Use
-sshver 2to force SSH2 protocol (more secure) - Enable verbose logging (-v4) to debug connection issues
- Implement proper timeout and retry logic in scripts
- Restrict SSH access at firewall and server level
- Regularly update PuTTY to latest version
Performance Tips
- Use
-batchmode for non-interactive execution (faster) - Reuse SSH connections with connection pooling
- Implement connection caching for multiple commands
- Use
-Nflag for tunnels (reduces overhead) - Minimize command verbosity to reduce traffic
- Test connection speed with
-connectionattempts - Use parallel execution for multiple servers
- Implement exponential backoff for retries
Integration Examples
With cron for scheduled tasks
# /etc/cron.d/plink_tasks
0 3 * * * root plink -i /root/.ssh/id_rsa -batch user@server "backup_command" >> /var/log/plink_backup.log 2>&1
With systemd for tunneling service
[Unit]
Description=SSH Tunnel to Database
After=network.target
[Service]
Type=simple
User=app
ExecStart=/usr/bin/plink -N -i /home/app/.ssh/id_rsa -L 3306:db:3306 user@bastion.example.com
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
Related Tools
- OpenSSH - Native SSH client (Linux/macOS alternative)
- WinSCP - GUI SFTP/SCP client with scripting
- Putty - Full PuTTY suite with GUI client
- SSH Key Management - Key generation and conversion
Last updated: 2026-03-30