Zsh Tutorial: curl, SSH, rsync — Network Like a Pro #23
Video: Zsh Tutorial: curl, SSH, rsync — Network Like a Pro #23 by Taught by Celeste AI - AI Coding Coach
Zsh Lesson 23: curl, SSH, rsync — Network Like a Pro
curl urlGET,-X POST -d dataPOST,-H "Header"custom headers,-o filesave,-Lfollow redirects,-Iheaders only.ssh user@host,~/.ssh/configfor shortcuts.rsync -av src/ dst/for fast incremental sync;scp file user@host:for one-off copies.
Three of the most-used network tools. Each does one thing well.
curl: GET request
curl -s https://httpbin.org/get | jq
curl url fetches and prints to stdout. -s suppresses progress meter; -S keeps errors visible.
Save to file
curl -s -o page.html https://example.com
curl -s -O https://example.com/file.zip # use remote filename
-o file saves to a specific name. -O (capital) uses the URL's basename.
Follow redirects
curl -sL https://httpbin.org/redirect/2
By default, curl shows the redirect response (302) and stops. -L follows.
Headers only
curl -sI https://example.com
# HTTP/2 200
# content-type: text/html
# ...
-I issues a HEAD request. Useful for checking content-type, size, or status.
Custom headers
curl -s -H "User-Agent: MyApp/1.0" -H "Accept: application/json" https://api.example.com
Each -H adds one header. Common ones:
User-Agent— identify your client.Accept— tell server what format you want.Authorization: Bearer TOKEN— auth.Content-Type: application/json— for POST bodies.
POST with JSON
curl -s -X POST \
-H "Content-Type: application/json" \
-d '{"name":"Alice","role":"admin"}' \
https://httpbin.org/post | jq '.json'
-X METHOD sets the HTTP method (default GET). -d data is the body — sets Content-Type: application/x-www-form-urlencoded by default, override with -H.
For data from a file:
curl -X POST -H "Content-Type: application/json" \
--data @payload.json \
https://api.example.com/items
@filename reads the body from a file.
Form data (multipart)
curl -X POST \
-F "name=Alice" \
-F "avatar=@photo.jpg" \
https://api.example.com/upload
-F field=value sends multipart/form-data. @filename for file uploads.
Authentication
# Basic auth
curl -u user:pass https://api.example.com
# Bearer token
curl -H "Authorization: Bearer $TOKEN" https://api.example.com
# Cookie auth
curl -b "session=abc123" https://example.com
For OAuth or complex flows, httpie (http) is friendlier; curl is the everywhere-tool.
Query parameters
curl -s "https://httpbin.org/get?search=zsh&limit=5" | jq '.args'
Build into the URL. For dynamic values, use --data-urlencode:
curl -G --data-urlencode "q=hello world" "https://api.example.com/search"
-G (capital) sends the data as query string instead of body.
Response info: -w
curl -s -o /dev/null -w "Status: %{http_code} Time: %{time_total}s\n" https://example.com
# Status: 200 Time: 0.12s
-w writes formatted info after the request. Useful variables:
%{http_code}— status.%{time_total}— total seconds.%{size_download}— bytes received.%{url_effective}— final URL after redirects.
For health checks and monitoring scripts.
Common API workflows
# Pretty-print API response
curl -s https://jsonplaceholder.typicode.com/users/1 | jq
# Just a few fields
curl -s https://jsonplaceholder.typicode.com/users/1 | jq '{name, email}'
# Check status
curl -fsS https://api.example.com/health || echo "Health check failed"
# Save with auth
curl -fsS -H "Authorization: Bearer $TOKEN" \
https://api.example.com/data \
-o data.json
-f causes curl to fail (non-zero exit) on HTTP errors. Combined with -S, you get a clean error message and a usable exit code.
SSH: log in to remote machines
ssh user@host
ssh -p 2222 user@host
ssh -i ~/.ssh/special_key user@host
ssh user@host opens an interactive shell on host. -p for non-default port; -i for a specific key.
SSH config: ~/.ssh/config
For machines you connect to often:
# ~/.ssh/config
Host myserver
HostName 192.168.1.100
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
Host *.example.com
User alice
IdentityFile ~/.ssh/work_key
Now:
ssh myserver
ssh prod.example.com
No flags needed — config provides them. chmod 600 ~/.ssh/config for permissions.
SSH keys
# Generate a new key
ssh-keygen -t ed25519 -C "alice@laptop"
# Copy to remote
ssh-copy-id user@host
# Test
ssh user@host # should not prompt for password
ed25519 is the modern default — small and fast. rsa 4096 is the older alternative.
After ssh-copy-id, your public key is in the remote's ~/.ssh/authorized_keys. Subsequent SSH connections use the key, no password.
Running remote commands
ssh myserver "uptime"
ssh myserver "df -h | head"
ssh myserver "tail -f /var/log/app.log"
Pass a command as an argument; SSH runs it and exits.
For multi-line scripts:
ssh myserver << 'EOF'
cd /var/www
git pull
systemctl reload nginx
EOF
The heredoc becomes the remote shell's stdin.
SSH tunnels
# Local forwarding: localhost:8080 → server:80
ssh -L 8080:localhost:80 myserver
# Remote forwarding: myserver:9000 → laptop:3000
ssh -R 9000:localhost:3000 myserver
# SOCKS proxy
ssh -D 1080 myserver
For accessing resources behind firewalls or for development. The first form lets curl localhost:8080 on your laptop reach server:80.
scp: copy files over SSH
scp file.txt user@host:/remote/path/
scp user@host:/remote/file.txt ./
scp -r folder/ user@host:/remote/path/
Like cp but over SSH. -r for directories.
For modern use, prefer rsync — handles large files better, resumes on failure, syncs incrementally.
rsync: incremental sync
rsync -av source/ destination/
rsync -av source/ user@host:/remote/path/
rsync -av user@host:/remote/path/ ./local/
-a (archive) preserves permissions, ownership, timestamps, recursion. -v verbose.
Trailing slash matters:
rsync -av src/ dst/— copies contents of src into dst.rsync -av src dst/— copies src itself into dst (dst now has src/).
The first is usually what you want.
rsync flags worth knowing
-a archive (recursive + preserve metadata)
-v verbose
-z compress during transfer (good for slow links)
-h human-readable sizes
-P show progress + allow resume (= --partial --progress)
--delete delete files in dst not in src (DANGEROUS)
--exclude PATTERN
--dry-run show what would be transferred
Common backup pattern:
rsync -avhz --delete --exclude='.git' --exclude='node_modules' \
~/projects/ \
myserver:~/backups/projects/
A real deployment script
#!/usr/bin/env zsh
set -euo pipefail
REMOTE=myserver
APP_DIR=/var/www/myapp
# Build locally
echo "Building..."
npm run build
# Sync (excluding node_modules)
echo "Syncing to $REMOTE..."
rsync -avz --delete \
--exclude='node_modules' --exclude='.env' \
dist/ "$REMOTE:$APP_DIR/"
# Reload service
ssh "$REMOTE" "sudo systemctl reload myapp"
# Verify
if curl -fsS "https://example.com/health" > /dev/null; then
echo "Deploy succeeded"
else
echo "Health check failed!" >&2
exit 1
fi
Standard deployment shape: build, sync, reload, verify.
Network diagnostics
ping example.com # is it reachable?
ping -c 3 example.com # 3 packets
nc -zv host 80 # is port 80 open?
telnet host port # interactive
nslookup example.com # DNS lookup
dig example.com # detailed DNS
traceroute example.com # route trace
mtr example.com # interactive trace + ping (install)
netstat -an | grep LISTEN # listening ports (Linux)
lsof -i -P -n | grep LISTEN # listening ports (macOS)
These are your first stop when a network thing isn't working.
Common stumbles
curl https://... but https not in $PATH. The shell tried to run a command named https://.... Make sure to call curl explicitly.
Body posted but server says "missing field". Wrong Content-Type. -d defaults to form-encoded. For JSON, add -H "Content-Type: application/json".
curl exit 0 even on 404. Default exit is 0 for any HTTP response. Use -f for "fail on 4xx/5xx."
Permissions on SSH keys. chmod 600 ~/.ssh/id_* and chmod 700 ~/.ssh. SSH refuses to use keys with looser permissions.
rsync trailing slash mistake. rsync -av src dst/ vs rsync -av src/ dst/ — different. Test with --dry-run first.
--delete destroys data. Removes from dst whatever isn't in src. With a typo (wrong src), you can wipe a server. Always --dry-run first.
SSH config not picked up. Permissions issue: chmod 600 ~/.ssh/config.
Slow rsync over slow link. Add -z for compression. Watch progress with -P.
What's next
Lesson 24: cron and launchd. Schedule tasks.
Recap
curl url — GET; -X POST -d data, -H header, -o file, -L follow, -I headers, -f fail on HTTP errors, -w response info. SSH: ~/.ssh/config for shortcuts; ssh-copy-id for keys; remote command via arg or heredoc. rsync -av src/ dst/ (mind trailing slash); --delete and --dry-run. For deployment: build, sync, reload, verify. For diagnostics: ping, nc, dig, lsof.
Next lesson: cron and launchd.