WeTTy the Fast SSH Terminal in your Browser

Ever wanted to do some simple correction to your server, or needed to access it from a machine that (hold on now, this going to be scary) didn’t have any terminal or other tools to do so?

Or were on a network that blocked the port that your server was using for SSH? Or you only had HTTPS access to your server?

Well, here’s an easy way around that. WeTTy (Web + TTy) is “an alternative to ajaxterm and anyterm but much better than them because WeTTy uses xterm.js which is a full fledged implementation of terminal emulation written entirely in JavaScript. WeTTy uses websockets rather then Ajax and hence better response time.”

In other words, it’s a fast shell in your browser.

And true to what it says, I have tried it with htop, tmux, vim, and all of my regular program, as well as used it with my daily routines and system installations. It’s worked great.

And the connection is very stable. I’ve been logged in the whole day without an issue. I still use Tmux, but that’s more for the multiple terminals it provides.

WeTTy screenshot

What we will do

To set this up, we are going to use Docker compose to do so, to make it simple. You’ll need Docker and Docker Compose installed, and that you have Traefik installed and setup. And of course you’ll need OpenSSH installed and running on the server.


I’ll also show you how to (optionally) setup two factor authentication (2FA) for this, as an added security measure.

How we will login

While we can have WeTTy connect directly to the server’s SSH, I prefer to have another line of defense: a separate container that must be logged into, which will then force login to the server’s SSH.

Broken down:

  1. Login to website. (Used to deter bots. Should not be the same as the below.)
  2. Login to SSH container. (Again, used more to deter. Does not give the user a shell).
  3. Login to Server (This login better be good.)
[Browser]---> [Traefik (Login 1)]-> [WeTTy]-> [wetty-ssh (Login 2)]-> [Server (Login 3)]

It’s a lot of logins. I plan on seeing if there’s a better way to do this in the future, but I’ve been using it for a long time now, and it’s fairly simple to login. And I prefer the peace of mind.


Remember that this is in a browser. You’ll want to make sure your browser is secure and up to date.


You must use SSL (TLS) on this setup if it’s over the internet, from your browser to the server. Otherwise anything you type (passwords, codes, your dog’s name) will be visible, and, just like in the days of telnet, others can inject commands into your connection that will then be run on the server. You have been warned.

The examples below are configured to use the SSL that Traefik is using, assuming that you’ve configured Traefik to use an SSL certificate for the domain.

Also, the connection between Traefik and WeTTy is still encrypted, with certificates that WeTTy generates when it first starts up.


Feel free to set this up how you want to, but we’ll do the above in this article.

I have a git repo for this. You can either clone it or copy/make the 3 needed files that will be mentioned below.

git clone https://github.com/bagley/wetty.git

Docker compose

The first thing we are going to do is to make our docker-compose.yml. It’s in the above repo, or you can download it

wget https://raw.githubusercontent.com/bagley/wetty/master/docker-compose.yml

Or copy and save the full file.

version: "2.3"


    image: mydigitalwalk/wetty:latest
    # image: butlerx/wetty
    restart: always
    mem_limit: 300M
    container_name: wetty
#    tty: true
      - .env
      SSHHOST: 'wetty-ssh'
      SSHPORT: 22
      NODE_ENV: 'production'
      - traefik_proxy
      - default
      - wetty-data:/home/node
      - /etc/localtime:/etc/localtime:ro
      - "traefik.enable=true"
      - "traefik.backend=wetty"
      - "traefik.frontend.rule=Host:${DOMAINNAME}"
      - "traefik.frontend.auth.basic.usersFile=/shared/.htpasswd"
      - "traefik.protocol=https"
      - "traefik.port=3000"
      - "traefik.docker.network=traefik_proxy"
      - "traefik.frontend.headers.SSLRedirect=true"
      - "traefik.frontend.headers.STSSeconds=315360000"
      - "traefik.frontend.headers.browserXSSFilter=true"
      - "traefik.frontend.headers.contentTypeNosniff=true"
      - "traefik.frontend.headers.forceSTSHeader=true"
      - "traefik.frontend.headers.SSLHost=example.com"
      - "traefik.frontend.headers.STSIncludeSubdomains=true"
      - "traefik.frontend.headers.STSPreload=true"
      - "traefik.frontend.headers.frameDeny=true"

    image: mydigitalwalk/wetty-ssh:latest
    # this is needed so wetty can connect to it
    container_name: 'wetty-ssh'
    restart: always
    mem_limit: 200M
      - env-wetty-ssh
      - wetty_ssh-data:/data
      - /etc/localtime:/etc/localtime:ro
      - default

      name: traefik_proxy

    name: wetty-data
    name: wetty_ssh-data

Now let’s go through what needs to be configured in it.

WeTTy app

First we have the wetty application. This is what Traefik connects to.

It needs a DOMAINNAME and a BASEURL, so it can tell Traefik what traffic to direct to itself. It uses the .env file for these settings. You can either rename .env.example to .env in the repo, or use the file below. Then set the domain url, and the base url, which will be accessible from https://{DOMAINNAME}{BASEURL}.

# In .env file

The above example would be reachable from https://console.example.com/console/.


The htpasswd file will prompt us for a username and password to open the site. This helps keep bots away. The path we have here is where Traefik will see it (/shared/wetty-htpasswd). In my tutorial of Traefik, I made a volume for the /shared folder. If yours is different, you’ll need to adjust it accordingly.

     - "traefik.frontend.auth.basic.usersFile=/shared/wetty-htpasswd"

But either way, that file will need to have a username/password in the htpasswd format. The command to get that is:

htpasswd -nb YourUser YourPassPhrase

Of course, you don’t have to have a htpasswd, but it’s a nice extra layer of security, and helps keeps scanning bots away.

wetty-ssh app

When the wetty app connects to wetty-ssh, the wetty-ssh prompts the user for a username and password.

The username, password, and ssh connection information is configured in the env-wetty-ssh file. The container uses this when it starts up, making a user without the need to rebuild the container.

Why two files, one for each container? Because the public facing container should not already have your username/password to login to the second, in case someone were to break into it. It would make it too easy to get into the ssh one.

You can copy this from the repo’s env-wetty-ssh.example or use the below.

I would suggest you don’t set these to your actual ssh username/password.


Now when we do login to wetty-ssh, it will need the host/port/username of the destination SSH server, so it can give you a prompt for that.

We also set this up in the env-wetty-ssh file:


So when you’re done, env-wetty-ssh should have something like



Be sure to change the above username and password. And don’t set the SSHHOST to localhost or as this is a container, and that would just have it connect back to itself.

Traefik network

The last part is the Traefik network. If you change this, be sure to change the other references to traefik_proxy in the file.

      name: traefik_proxy


You don’t need to do anything with the volumes, but as an FYI, we have volumes on the apps to store the SSH keys, known_hosts files, and other files needed across recreates/updates. Docker Compose will automatically create the needed volumes for each app. They are called wetty-data for the wetty container, and wetty_ssh-data for the wetty-ssh container.

So again you don’t need to do anything for this part. Of course, you are welcome to change them. Just make sure they go to the same directories in the containers.

Configure Server’s SSH

Now you may not need to do this. But I did as I by default disable password authentication. Just keys. Keys are awesome.

So this is going to go by the assumption that you just use keys, and you only want to keep using keys for your connections.

And before we start, make sure you are logged into the server with at least two connections/terminals. The following instructions will require a restart of SSH, which could lock you out of the server if the configuration isn’t right and SSH can’t restart. (We’ll use ssh -t to verify, but still be careful). Just know that when SSH is restarted, if it doesn’t come back up, don’t panic. Your current session should stay open. Simply the fix the issue and/or revert the changes as soon as you can and restart SSH.

On that note, let’s first take a backup of the configuration. In the SSH server:

sudo cp -a /etc/ssh/sshd_config{,-bak}

Then open it in your favorite editor

sudo nano /etc/ssh/sshd_config
# or
sudo vim /etc/ssh/sshd_config

Also, we don’t need PasswordAuthentication yes in the main section, as we will be putting it in the Match section. If you have it here, you may consider removing/disabling it and using ssh keys, if you can. They are a lot safer IMHO.

We will only allow the above user you used for SSHUSER to be able to connect using a password, but only from a local IP address in the range “,,”. You can modify this range as needed.

At the end of the file, add these lines:

Match User Your.SSH.User Address,,
        AllowUsers YourUser
        PasswordAuthentication yes

I’ve added AllowUsers as I use it in the main section to limit which users can login, so I have to put it in the Match section to allow just user to use password authentication. It can be removed if needed.

Once you’ve done that, save it, and let’s test the configuration. Run:

sudo sshd -t

If there are any errors with the file, it will let you know. Resolve any errors it tells you before continuing.

Now let’s restart it (I’m assuming your service is sshd, but may be ssh or openssh. Run sudo systemctl | grep ssh if you don’t see it.)

sudo systemctl restart sshd

And then in a new terminal verify that you can still login to the server like you normally do. If you can’t, check the server’s logs for any info ()journalctl -f). If it still won’t move the above backup file back into place and restart SSH.

Start it up

Now that we have the Docker compose file, and SSH is configured, let’s start WeTTY

docker-compose up -d

And check the logs

docker-compose logs -f

It should start without any errors. Go to the URL for your WeTTy setup (that you set in the docker-compose.yml).


Remember that once WeTTy is up, Traefik does not need to be restarted for the settings to apply. It just magically knows it’s there.

Once you enter the htpasswd username/password, you should see a “Enter your username:” prompt. Enter the username and password for the container, and you’ll then see a prompt for the password of your server’s user. Here’s a screenshot of my login prompts

wetty login

The above screenshot has 2FA enabled, so that’s why it shows the Verification code:. Otherwise it would have only prompted with a password.

If you like how the system give me a notice before I login to it, here’s a simple setup on how to do it.

Bonus: Set up 2FA for Added Security

We can easily setup two factor authentication for this. We will use Google Authentication to do so, though you could use any other authentication method that has a PAM module.

The Google Authenticator, like all good authenticators, does not rely on an internet connection. It uses mathematics to generate a one time password that changes every 30 secs.

SSH Challenge Response

First, we need to enable ChallengeResponseAuthentication in our SSH configuration. The Open up /etc/ssh/sshd-config in your editor, and find the line with this setting and set it to yes.

ChallengeResponseAuthentication yes

Do not put this in the Match section, as it’s not supported in there.

Also make sure that UsePAM is enabled.

UsePAM yes

Verify your SSH configuration

sudo sshd -t

Restart it if it’s valid

systemctl restart sshd

And once again in a new terminal verify that you can still login like you normally do.

Install google-authenticator

On your phone, search for the “Google Authenticator” app and install it, if you haven’t already. (Or you can install or use any 2FA app that supports this, as the 2FA is mathematically generated, and does not need Google or any other provider for the server to connect to.)

Then on the server, install google-authenticator, use your package manager to install it.

# Debian / Ubuntu
sudo apt-get install libpam-google-authenticator

# Fedora / Centos / Redhat
sudo dnf install libpam-google-authenticator

# Arch
sudo pacman -Sy libpam-google-authenticator

On the server, switch to the user that WeTTy will login as, and run the authenticator.

[[email protected] ] google-authenticator

This will setup the needed file for the user, and give you a code/image. Scan this with the Google Authenticator app, and it’ll add it to the app. It should also ask you to enter in the code on the app, to verify that it’s setup properly.

Also, make sure the permissions are correct. If these aren’t, the PAM login module will not use it and won’t even give you a prompt for the 2FA.

chmod 600 /home/user/.google_authenticator

Add it to PAM

You now have the authenticator setup. But we need to add it to your server’s login configuration, /etc/pam.d/sshd, so PAM will know to use it. This will only be for the server’s SSH logins. Console and other logins will not be effected.

First let’s make a backup

sudo cp -a /etc/pam.d/sshd{,-bak}

Open it up in an editor

nano /etc/pam.d/sshd

vim /etc/pam.d/sshd

And add this line to bottom of the file:

auth required pam_google_authenticator.so nullok

The above will still allow password logins via SSH on the server. If you want to enforce 2FA for all users

auth required pam_google_authenticator.so

Go ahead and save it. And (you know what’s coming) in a different terminal verify that you can still login with SSH. If you’re using a key to login, you should not get a prompt for a “Validation code”, only if you’re using a password.

Test it out

Go to back to WeTTy and test it out. All should be the same, except now you should be prompted for a validation code after entering your user’s password. Open the authenticator app and enter the code it gives you.


There you have it. You now can login to your server from a browser. Have fun with it!