Plain-text guide to SSH Tunneling ----------------- SSH Options & Tools ----------------- Good options: -N (Do not execute remote command, just do the tunnel) -f (Run SSH tunnel as a daemon (in the background); sometimes requires -N) -C (compression) -g (allow external hosts to use this tunnel (hosts other than 127.0.0.1) ) -q (quiet; only fatal errors are displayed) -T (don't bother setting up a pseudo-TTY on the remote system; related to -N but not implied by -N) In order to forward global ports through a reverse tunnel, sshd must set GatewayPorts in /etc/sshd_config (or /etc/ssh/sshd_config): GatewayPorts clientspecified Tools: You can keep a tunnel alive (restarting itself on disconnection or failure) with this: while ! ( ); do sleep 10; done; Example: Set up automatic key-based authentication (see https://help.webfaction.com/177) and then: while ! (ssh -N -R2222:localhost:22 servuser@theserver &); do sleep 5; done; If you don't mind installing a third-party program, the "autossh" tool is the best way to keep a tunnel open indefinitely. It listens on a special "monitoring port" (tunneled internally) in order to keep the tunnel alive. It also requires automatic key-based authentication, and is invoked as: autossh -M5115 -N -R2222:localhost:22 servuser@theserver & which will monitor on both client's and server's internal port 5115 and keep the tunnel open. The best tools to test tunnels are 'telnet' and 'netcat'. Get latest versions (otherwise you may have issues). To test a globally-bound port: [servuser@theserver ~]$ netcat -l -p1234 -s0.0.0.0 #Listen on port 1234, globally (binding to 0.0.0.0) [clntuser@theclient ~]$ telnet theserver 1234 #Then type "hello" To test a locally-bound port: [servuser@theserver ~]$ netcat -l -p1234 -s127.0.0.1 #Listen on port 1234, locally (binding to 127.0.0.1) [servuser@theserver ~]$ telnet localhost 1234 #Then type "hello" ---------------- Definitions ---------------- In this article we may have up to three machines to keep track of, and connecting from within the machine to a port (connecting to a port locally) is, in general, different than connecting to the same port on the same machine from the outside (connecting to that port remotely). So we need a good way to specify these three machines, their users, their ports, whether the connections are considered local or remote, and which machine each command should be run from. We have three machines. We'll say the computer you are sitting at is "theclient". There is another server somewhere else; call him "theserver". There is yet another computer elsewhere that we have access to and could use if we want to. Call this machine "middleman". We have one user on each machine: servuser@theserver clntuser@theclient midluser@middleman Next, we need to specify ports: theserver:4444 is port 4444 on theserver, for example. However we need to distinguish between whether those ports are being considered to be being accessed remotely or locally. First consider our own machine ("theclient"). 127.0.0.1 on our machine is, of course, considered "localhost" to our machine. We're going to title that "clnt_localhost". Now, there is another machine. From the outside, it's known as "theserver". Of course, if you're actually logged into "theserver", then that machine will refer to *itself* as "localhost". *That* "localhost" is going to be termed "serv_localhost". Similarly there is a third machine, "middleman", which refers to itself as "localhost", and we will call *that* localhost "midl_localhost". So, we have three machines, and for the sake of clarity, I will reiterate the related terminology: theclient (this is the machine initiating the reverse tunnel) theserver (this is the remote machine you are connecting to and opening a port on) middleman (this is another machine we can use as a middle man, in some examples) serv_localhost (this is 127.0.0.1 on the remote machine) clnt_localhost (this is 127.0.0.1 on the client machine) midl_localhost (this is 127.0.0.1 on the middleman machine) theserver:4444, then, means "port 4444 on theserver, connecting remotely". You would be on theclient, for example, and connect to theserver:4444 serv_localhost:4444 refers to connecting to "localhost:4444" while logged in on theserver machine. Lastly, we need to specify where each command is to be run. Here are the shell prompts on each machine: [clntuser@theclient ~]$ [servuser@theserver ~]$ [midluser@middleman ~]$ There are also some synonyms to be aware of: "remote", "external", "outside", "global" port 4000 refers to themachine:4000 "local", "internal", "inside" port 4000 refers to localhost:4000 This specific terminology will avoid ambiguity. You will thank me later. ------------------------------------------------- Tunnels -------------------------------------------------- Local loopback tunnel (local port forwarding): [clntuser@theclient ~]$ ssh -L127.0.0.1:2222:localhost:22 localhost # explicitly bind to 127.0.0.1 (0.0.0.0 is synonym for -g) [clntuser@theclient ~]$ ssh -L2222:localhost:22 localhost # bind to 127.0.0.1 implicity Effect(s): clnt_localhost:2222 --> clnt_localhost:22 Discussion: Local loopback tunnel, where you forward clnt_localhost:2222 to clnt_localhost:22, which means you could forward ports for a program with hard-coded binding. Say you have a web interface that only listens on port 5000 (http://localhost:5000), but you have some software on the same machine that only takes a single IP address and no port (it assumes port 80). You could use this tunnel to connect 80 and 5000 so the two stubborn programs can communicate. It has *not* opened any ports visible from the outside world (see -g below for that), so this is not generally a security problem. This can be used with -g as follows (let's do it on theserver for this example): [servuser@theserver ~]$ ssh -g -L2222:localhost:22 localhost Effect(s): serv_localhost:2222 --> serv_localhost:22 theserver:2222 --> serv_localhost:22 Discussion: This is used when you want to connect to the theserver on the newly-opened port. Now, connecting from the outside on port 2222 is the same as connecting (internally) from serv_localhost:22. It tunnels theserver:2222 --> serv_localhost:22 in addition to the normal serv_localhost:2222 --> serv_localhost:22 Someone outside (on a machine that is not "theserver"; i.e.: you) can now connect to theserver:2222, and theserver will process that request as if it is coming from localhost:22. Since 22 is normally used for the SSH daemon, it means you can ssh into your server on port 2222 in addition to port 22 now. Use: [clntuser@theclient ~]$ ssh -p2222 servuser@theserver This is useful if your ISP is blocking port 22 (they don't like encryption?), and you want to connect through a different port. Even better (if you have root on theserver) is to use port 443 so it looks like a normal https:// page. And, best yet, use port 80 if "theserver" is not serving web content- then at first glance it just looks like you're just browsing a webpage! No ISP can get away with blocking port 80. As I said, connecting to myserver:2222 is now the same as connecting to serv_localhost:22, and not the same as connecting to myserver:22. Say, for example, we have the following command running: [servuser@theserver ~]$ ssh -L1111:localhost:22 localhost In this case, serv_localhost:1111 forwards to serv_localhost:22. But you can't ssh into myserver:1111 because no -g was used. We can then run: [servuser@theserver ~]$ ssh -g -L2222:localhost:1111 localhost to forward myserver:2222 to serv_localhost:1111 (and serv_localhost:1111 already forwards to serv_localhost:22 via the above command) So, this command (with -g) allows you to open an externally-viewable port and connect to a normally internal-only process. *That* can be a security problem, so be aware of it. Also note that many shared hosts block all global binding in general for this reason. ------------------------ Reverse tunnel (Remote-to-local tunnel): [clntuser@theclient ~]$ ssh -R4444:localhost:22 servuser@theserver # bind address depends on GatewayPorts in sshd_config Effect(s): serv_localhost:4444 --> clnt_localhost:22 Discussion: This is a reverse tunnel. What it does is it opens a local port on a remote machine, and forwards all connections there to your local machine on another specified local port. In this example, the remote machine is "theserver". The command given opens port 4444 on theserver. This port 4444 is viewable only from the inside, and it tunnels serv_localhost:4444 --> clnt_localhost:22 This is useful if you have a restrictive firewall in front of theclient, so incoming ssh (port 22) connections fail. Outgoing connections from theclient are Okay, though, so we open a reverse tunnel from theclient to theserver, forwarding serv_localhost:4444 to clnt_localhost:22. Notice i said "we open a reverse tunnel from theclient to theserver", if that sounds backwards, it is: that's why this is called a "reverse tunnel". Okay. Now, someone logged into theserver can connect to serv_localhost:4444 and log into the ssh daemon on theclient, bypassing the firewall! Use: [servuser@theserver ~]$ ssh -p4444 clntuser@localhost Here is another way to use this: [clntuser@theclient ~]$ python -m SimpleHTTPServer & # Serve current directory tree at http://localhost:8000/ [clntuser@theclient ~]$ ssh -R4444:localhost:8000 servuser@theserver # tunnel serv_localhost:4444 --> clnt_localhost:8000 [servuser@theserver ~]$ wget http://localhost:4000/file.txt # Get file from home What's the point? (why not just open a reverse tunnel, ssh in, and use scp?). Probably nothing. But these three lines are very illustrative about what's going on. NOTE: -g has no effect on reverse tunnels: [clntuser@theclient ~]$ ssh -g -R4444:localhost:22 servuser@theserver # ----- Does NOT work. I consider this a bug. Effect(s): serv_localhost:4444 --> clnt_localhost:22 #theserver:4444 --> clnt_localhost:22 # <--- Does NOT happen. If you want theserver:4444 to bind correctly, you must specify GatewayPorts in /etc/sshd_config (or /etc/ssh/sshd_config). However, if you specify "GatewayPorts clientspecified", then you can use a bind address of 0.0.0.0 instead of -g, and it works. ------------------- Local-to-Remote Tunnel [clntuser@theclient ~]$ ssh -L2001:localhost:5000 servuser@theserver Effect(s): clnt_localhost:2001 --> serv_localhost:5000 Discussion: start a tunnel from theclient's internal port 2001 to theserver's internal port 5000. This could be useful if theserver hosts internal web content on port, say, 5000, and that is not accessible from the outside. Now, you can acces that website by going to http://localhost:2001/ Similiarly, you could do: [clntuser@theclient ~]$ ssh -L2001:theserver:22 localhost Effect(s): clnt_localhost:2001 --> theserver:22 Discussion: Forwards local 2001 to remote host's port 22, on the outside. You can't connect to a server port only accessible from the inside, but you also don't need to know host's login to point the port there. Using this, you could "ssh -p 2001 servuser@localhost" to connect to the remote computer, and that would be equivalent to "ssh -p 22 servuser@theserver". This is also useful like this: "ssh -L2001:google.com:80 localhost" Now, http://localhost:2001/ gives you google.com's web page. -------------------------------- Multiple Local-to-Remote Tunnels in one command: [clntuser@theclient ~]$ ssh -L8025:smtp.comcast.net:25 -L8110:mail.comcast.net:110 localhost Effect(s): clnt_localhost:8025 --> smtp.comcast.net:25 clnt_localhost:8110 --> mail.comcast.net:110 This is in the case of an email client on a laptop, pointing to localhost:8025 for SMTP services, and localhost:8110 for POP3 services associated with a Comcast account. Note that you get NO encryption benefit here! (You need ssh to log into a remote server for that; this is purely for convenience.) In general, you can specify several tunnels in one SSH command and they will all take effect. The only stipulation is that you are not simultaneously trying to connect to more than one remote host. -------------------- Dynamic tunnel: (SOCKS5 proxy) [clntuser@theclient ~]$ ssh -D8080 servuser@theserver Effect(s): clnt_localhost:8080 <== SOCKS5 PROXY ==> theserver ==> intended destination This sets up a SOCKS5 proxy through theserver. What that basically means is that the server acts as you, and *initiates connections on your behalf*. This is different than a tunnel, which always receives connections and then forwards data. The SOCKS5 proxy can initiate connections to foreign hosts, and then tunnel the data. Therefore, i could send a the request http://www.google.com through the SOCKS5 proxy. Then, theserver will initiate the HTTP request to www.google.com for me. It will connect, and then forward all data back to me through the SOCKS5 proxy. This is beneficial because it masks your identity (all communication between theclient and theserver is encrypted) and it looks like (to google.com) that you really are theserver. It has no way of knowing that the data it sends is forwarded to theclient. This bypasses internet filtering and many firewalls, as well as increasing security if "theclient" is located in a dangerous location while "theserver" is in a safe location. This can be used easily with Firefox: run the ssh command, and then in Firefox->Tools->Options->Advanced->Network->Settings set "Manual proxy configuration" to SOCKS Host "127.0.0.1" port "8080". The "QuickProxy" Firefox add-on can make it easy to turn on and off the proxy. --------------------------------- Two-hop-tunnel: ssh -N -p44 -L2001:localhost:22 -o "ProxyCommand ssh midluser@middleman nc -w 5 %h %p" servuser@theserver Effect(s): clnt_localhost:2001 --> middleman:44 --> serv_localhost:22 Discussion: set up a tunnel from destination machine port 80 to localhost 2001, via a second (middleman) machine. This is handy when middleman is the only machine authorized to connect to theserver (i.e. it is a jumphost) and you want to access it from elsewhere. Note: usually middleman listens for ssh connections on port 22. I only specified port 44 to be explicit about where the 22 and 44 are coming from. -------------------------------------------------------- Tunnel Reference -------------------------------------------------------- Notice: all of these commands are run from theclient. To use the commands on theserver, just put yourself in theclient's shoes. Same-Machine Tunnels: clnt_localhost:2222 --> clnt_localhost:3333 [clntuser@theclient ~]$ ssh -N -L2222:localhost:3333 localhost theclient:2222 --> clnt_localhost:3333 [clntuser@theclient ~]$ ssh -N -g -L2222:localhost:3333 localhost clnt_localhost:2222 --> theclient:3333 [clntuser@theclient ~]$ ssh -N -L2222:theclient:3333 localhost theclient:2222 --> theclient:3333 [clntuser@theclient ~]$ ssh -N -g -L2222:theclient:3333 localhost Cross-Machine Tunnels: clnt_localhost:2222 --> serv_localhost:3333 [clntuser@theclient ~]$ ssh -N -L2222:localhost:3333 servuser@theserver serv_localhost:2222 --> clnt_localhost:3333 [clntuser@theclient ~]$ ssh -N -R2222:localhost:3333 servuser@theserver #GatewayPorts no or GatewayPorts clientspecified theclient:2222 --> serv_localhost:3333 [clntuser@theclient ~]$ ssh -N -g -L2222:localhost:3333 servuser@theserver serv_localhost:2222 --> theclient:3333 [clntuser@theclient ~]$ ssh -N -R2222:theclient:3333 servuser@theserver #GatewayPorts no or GatewayPorts clientspecified clnt_localhost:2222 --> theserver:3333 [clntuser@theclient ~]$ ssh -L2222:theserver:3333 localhost theserver:2222 --> clnt_localhost:3333 [clntuser@theclient ~]$ ssh -N -R0.0.0.0:2222:localhost:3333 servuser@theserver #GatewayPorts yes or GatewayPorts clientspecified theclient:2222 --> theserver:3333 [clntuser@theclient ~]$ ssh -N -g -L2222:theserver:3333 localhost theserver:2222 --> theclient:3333 [clntuser@theclient ~]$ ssh -N -R0.0.0.0:2222:theclient:3333 servuser@theserver #GatewayPorts yes or GatewayPorts clientspecified