SSH via multiple hosts or bastions

Method 1 – onion (nested tunnels)

With OpenSSH 7.3 and later:

Host webserverA
    ProxyJump bastionA,bastionB

The same via command line:

$ ssh -J bastionA,bastionB webserverA

Alternatively (also with 7.3; don't mix this and above):

Host webserverA
    ProxyJump bastionB

Host bastionB
    ProxyJump bastionA

With older versions – mostly identical (but doesn't automatically copy options like ssh -v):

Host webserverA
    ProxyCommand ssh bastionB -W %h:%p

Host bastionB
    ProxyCommand ssh bastionA -W %h:%p

This method initiates all connections locally, setting up a ssh -W tunnel to each step. Therefore authentication happens locally (ForwardAgent and GSSAPIDelegateCredentials are not required) and your local .ssh/config applies to each step as well. Server-side, only basic "TCP forwarding" support is needed, same as when using -W or -L.

However, each layer adds extra overhead, since it ends up carrying SSH in SSH in SSH in SSH.

Note that each host, except for the outermost one, lists a ProxyCommand through the server immediately before it. If you had 3 servers, you would use [webserverA via bastionC], [bastionC via bastionB], and [bastionB via bastionA].

Method 2 – hop by hop

Host webserverA
    ProxyCommand ssh bastionA -A ssh bastionB -W %h:%p

This method initiates connections hop by hop, running ssh on each hop to connect to the next one. Therefore a ssh-agent and ForwardAgent must be enabled (or GSSAPIDelegateCredentials if you use Kerberos); any other special .ssh/config settings must be copied to all bastion hosts.

On the other hand, it incurs less protocol overhead (max. two layers at every step).

(Edit: added -A to always request agent forwarding.)