Quick takeaway: AWS IoT Secure Tunneling lets you SSH into IoT devices behind firewalls without opening inbound ports. You create a tunnel via CLI or console, the device connects outbound over port 443, and you get a bidirectional session. This guide covers the CLI workflow, quick vs manual setup, local proxy configuration, and common troubleshooting scenarios.
When a Raspberry Pi in a factory starts sending bad temperature readings at 2 AM, you do not want to drive 200 miles to fix a config file. But the firewall blocks all inbound SSH — and your ops team cannot reach the device.
AWS IoT Secure Tunneling solves this. The device initiates an outbound WebSocket connection to AWS (port 443, already allowed by most firewalls). You open a tunnel from your side, and AWS brokers a secure, bidirectional session between you and the device. No inbound ports, no VPN, no jump host.
How AWS IoT Secure Tunneling Works
Three components:
- Tunnel: created via the AWS IoT console or
aws iotsecuretunneling open-tunnelCLI command. It has a source side (you) and a destination side (the remote device). - Local proxy: runs on both sides. On the destination device, it connects the tunnel to the local application (e.g., SSH on port 22). On your source machine, it maps a local port to the tunnel.
- Access tokens: two tokens are generated when the tunnel opens — one for the source, one for the destination. Each side uses its token to authenticate with the AWS secure tunneling service.
The flow: the destination device’s local proxy connects to AWS over WebSocket (outbound, port 443). You start the source proxy, which also connects outbound. AWS matches them through the tunnel ID and tokens, then relays traffic in both directions. From your perspective, it feels like ssh -p 5555 localhost — but the actual device is a thousand miles away.
Quick Setup vs Manual Setup
| Criteria | Quick Setup | Manual Setup |
|---|---|---|
| Tunnel creation | Default config, SSH only | Custom config, any TCP service |
| Access tokens | Destination token auto-delivered via MQTT | Manually download and manage both tokens |
| Local proxy | Browser-based, no installation | Requires local proxy binary on both sides |
| Best for | Quick one-off debugging | Automated ops, non-SSH services, CI/CD |
Prerequisites
- AWS CLI v2 installed and configured with
iotsecuretunnelingpermissions - Outbound port 443 allowed on the destination device’s firewall (non-negotiable — the tunnel uses WebSocket over TLS)
- For manual setup: the local proxy binary from aws-samples/aws-iot-securetunneling-localproxy
- For quick setup with auto token delivery: the device must be registered as an AWS IoT thing and connected via MQTT
- IAM permissions:
iotsecuretunneling:OpenTunnel,iotsecuretunneling:CloseTunnel,iotsecuretunneling:DescribeTunnel
Creating a Tunnel with the CLI
The aws iotsecuretunneling command group gives you full control. Here is the basic workflow.
Step 1: Open a tunnel
aws iotsecuretunneling open-tunnel \
--description "Debug factory sensor" \
--destination-config thingName=RemoteSensorA,services=SSH \
--timeout-config maxLifetimeTimeoutMinutes=60Code language: JavaScript (javascript)
This creates a tunnel and returns two access tokens — one for the source (you) and one for the destination (the device). The destination token is automatically delivered to RemoteSensorA over the MQTT topic $aws/things/RemoteSensorA/tunnels/notify if the device is connected.
Response:
{
"tunnelId": "01234567-89ab-cdef-0123-456789abcdef",
"tunnelArn": "arn:aws:iot:us-east-1:123456789012:tunnel/01234567-...",
"sourceAccessToken": "AQD...source-token...",
"destinationAccessToken": "AQD...dest-token..."
}Code language: JSON / JSON with Comments (json)
Step 2: Start the local proxy on the destination
On the remote device, the IoT agent (e.g., AWS IoT Device Client) receives the token and starts the local proxy in destination mode:
./localproxy -r us-east-1 -d 22 -t DESTINATION_ACCESS_TOKEN
The -d 22 tells the proxy to forward tunnel traffic to localhost port 22 (SSH). You can use any port for other services — 3389 for RDP, 8080 for a web dashboard, etc.
Step 3: Start the local proxy on your source machine
./localproxy -r us-east-1 -s 5555 -t SOURCE_ACCESS_TOKEN
The -s 5555 maps the tunnel to your local port 5555. Once both proxies are connected:
ssh -p 5555 pi@localhostCode language: CSS (css)
You are now logged into the remote device through the tunnel. No inbound firewall rules were changed.
Managing Tunnels with the CLI
List active tunnels
aws iotsecuretunneling list-tunnelsCode language: PHP (php)
Describe a specific tunnel
aws iotsecuretunneling describe-tunnel \
--tunnel-id 01234567-89ab-cdef-0123-456789abcdef
This shows the tunnel status (OPEN or CLOSED), creation time, and timeout configuration.
Rotate access tokens
If a token is leaked or a session is compromised, rotate it without closing the tunnel:
aws iotsecuretunneling rotate-tunnel-access-token \
--tunnel-id 01234567-89ab-cdef-0123-456789abcdef \
--client-mode SOURCE
Use --client-mode DESTINATION to rotate the device token. The old token stops working immediately.
Close a tunnel
aws iotsecuretunneling close-tunnel \
--tunnel-id 01234567-89ab-cdef-0123-456789abcdef
Tunnel Lifecycle and Timeouts
A tunnel goes through these states:
- OPEN: created and ready for connections
- CLOSED: manually closed or reached
maxLifetimeTimeoutMinutes
The default lifetime is 720 minutes (12 hours). You can set it as low as 1 minute or as high as 720 minutes with --timeout-config maxLifetimeTimeoutMinutes=N. There is no keepalive — if both proxies disconnect, the tunnel stays OPEN until the timeout expires. Always close tunnels you no longer need.
Quick Setup via AWS Console (No CLI)
For one-off debugging, the console quick setup skips the local proxy entirely.
- Go to AWS IoT → Tunnels hub
- Click Create tunnel → Quick setup
- Select the destination thing (e.g.,
RemoteSensorA) - Choose SSH as the service
- Click Create — the token is sent to the device via MQTT
- Use the in-browser CLI that appears to SSH directly from the console
The browser-based SSH is convenient but limited. For scripting, CI/CD pipelines, or non-SSH services, use the manual setup with the local proxy.
Security Model
- No inbound ports: the device never listens. Both sides connect outbound to AWS over TLS 1.2+.
- Token-based auth: each side authenticates with a unique, single-use access token. Tokens can be rotated without closing the tunnel.
- IAM policies: control who can open, close, describe, and list tunnels. Use resource-level permissions tied to specific thing names.
- CloudTrail audit: every
OpenTunnel,CloseTunnel, andRotateAccessTokencall is logged. - No VPC dependency: the service is regional and does not require VPC endpoints, though interface VPC endpoints are supported if you need private connectivity.
Troubleshooting
“Connection refused” from the local proxy
The destination proxy cannot reach the local application. Check that the service (SSH, RDP, etc.) is actually running on the port you specified with -d. Verify with netstat -tlnp on the device.
Tunnel opens but SSH hangs
Port 443 outbound is the most common culprit. Both sides need outbound access on port 443 to data.tunneling.iot.<region>.amazonaws.com. Test with:
curl -v https://data.tunneling.iot.us-east-1.amazonaws.comCode language: JavaScript (javascript)
A 200 or 404 response means connectivity works. A timeout means the firewall is blocking it.
Destination token not delivered to device
The device must be connected to AWS IoT Core via MQTT and subscribed to the reserved topic: $aws/things/<ThingName>/tunnels/notify. If the device is offline when the tunnel is created, the token is not queued — you will need to create a new tunnel or deliver the token manually.
Tunnel timeout too short for long sessions
Increase maxLifetimeTimeoutMinutes up to 720 (12 hours). For sessions longer than 12 hours, create a new tunnel when the old one expires — there is no way to extend a tunnel’s lifetime.
FAQ
What is AWS IoT Secure Tunneling?
AWS IoT Secure Tunneling lets you establish a secure, bidirectional connection to a remote IoT device behind a firewall without opening inbound ports. The device connects outbound to AWS over port 443, and you connect from your side. AWS brokers the session. It is commonly used for SSH, remote debugging, and configuration updates on devices in the field.
What is the difference between quick setup and manual setup?
Quick setup creates a tunnel with default SSH configuration and handles tokens automatically via MQTT — no local proxy installation needed. Manual setup requires the local proxy binary on both sides and manual token management, but supports any TCP service (not just SSH) and works without MQTT-connected things.
How do I open a tunnel using the AWS CLI?
Use aws iotsecuretunneling open-tunnel –destination-config thingName=MyThing,services=SSH –timeout-config maxLifetimeTimeoutMinutes=60. This returns source and destination access tokens. Start the local proxy on both sides with the appropriate token, then connect through the mapped local port.
How much does AWS IoT Secure Tunneling cost?
There is no additional charge for using secure tunneling. You pay only for the messages delivered over MQTT to send access tokens (standard AWS IoT Core messaging rates apply). If you send one tunnel-open notification per device per day, the cost is negligible for most fleets.
Can I rotate access tokens without closing the tunnel?
Yes. Use aws iotsecuretunneling rotate-tunnel-access-token –tunnel-id –client-mode SOURCE (or DESTINATION). The old token stops working immediately. This is useful when a token is accidentally exposed in logs or shared with the wrong person.
Bottom Line
AWS IoT Secure Tunneling replaces the VPN-and-jump-host dance with a single CLI command and a local proxy. It costs nothing extra, works through firewalls without changes, and gives you SSH — or any TCP service — to devices in the field.
The core workflow: open-tunnel → start local proxy on both sides → connect through localhost. Rotate tokens when needed, close tunnels when done. For more IoT operations context, see the AWS IoT Secure Tunneling developer guide and the official demo on GitHub.








Leave a Reply