Create Your Own Azure Bastion with Guacamole and Save $100+ a month

Photo by Footluz Blog on Unsplash

Microsoft Azure provides Azure Bastion service which is a jump server so you can securely access your virtual machines via its Azure Portal web interface without exposing SSH or RDP port. Here is its architecture:

Azure Bastion Architecture from Microsoft

This service is great except it costs $0.19 per hour. Combining with other components then this may costs you almost $150 a month. Bad news is you can’t stop it unless you delete it.

This post, I will show you how I create my own jump server on Ubuntu VM to replace Azure Bastion using Apache Guacamole which is an open source tool which provide similar functionalities (I wonder that Azure Bastion may be even built on top of it). The VM can be a small one that may costs only a few tens bucks per month.

Apache Guacamole

From its website, Apache Guacamole is a clientless remote desktop gateway that supports standard protocols like VNC, RDP, and SSH. Clientless means your clients don’t need to install anything but just use a web browser to remotely access your fleet of VMs.

The Guacamole comprises of two main components:

  • Guacamole Server which provides guacd which is like a proxy server for the client to connect to the remote server.
  • Guacamole Client which is a servelet container that user will log in and via web browser.
Guacamole Architecture from Guacamole Documentation

For more information about Guacamole, visit its architecture page.

As my disclaimer, installation is not simple as there are several components you need to install and configure before it is good enough to use. There may be simpler ways to deploy e.g. using Docker image or using this Helm chart but I haven’t tried them yet. Because of cost is my concern so I’d like to deploy on a small VM that may not run a Kubernetes cluster (and may be I just prefer to learn it in hard way :P).

Network Topology

You use Azure Bastion or a jump server because you want to secure your VMs behind so having the right network design is needed. Here it mine:

Network Topology Modified from Azure Network Watcher by Author

In my virtual network (VNet), I split into two subnets:

  1. snet-gateway where I will deploy Guacamole on a Ubuntu VM which has a public IP so its web interface can be reached from the Internet.
  2. snet-default where I will deploy my backend pool of VM and enable remote access via Guacamole only.

For the sake of security, you should configure Network Security Group (NSG) of your snet-gateway to limit inbound and outbound connections to/from Ubuntu VM in the same way as you do for AzureBastionSubnet. But in this example, I just associate NSG to the VM directly.

Network Security Group Inbound Rules from Microsoft Docs
Network Security Group Outbound Rules from Microsoft Docs

Once you setup your network then create the VMs. In this example, I create the following two VMs:

  1. vm-win10 in the snet-default which is the machine I will remote access to.
  2. vm-ubuntu1804 in the snet-gateway where I will install Guacamole and use it as a jump server. I choose B2s size which cost around $39 per month but, of course, you can change its size later.

Install Guacamole Server

Once your Ubuntu Server 18.04 is created then log in via SSH. You may need to expose SSH port publicly for now. You can change SSH port to something than 22 to make it more secure. See how to do it in my previous blog.

From its documentation, there’s no executable binary available for Guacamole server and you need to build it from source (unless you deploy from Docker image).

First you need to install build tools and all required dependencies for the build process. Missing some of them may result in missing features or build failure.

# Update & upgrade system
sudo apt-get update && sudo apt-get --yes upgrade

# Install build tools
sudo apt install --yes build-essential

# Install build dependencies
sudo apt install --yes libcairo2-dev libjpeg-turbo8-dev libpng-dev libtool-bin libossp-uuid-dev

# Install optional dependencies
sudo apt install --yes \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \
freerdp2-dev \
libpango1.0-dev \
libssh2-1-dev \
libtelnet-dev \
libvncserver-dev \
libwebsockets-dev \
libpulse-dev \
libssl-dev \
libvorbis-dev \

# Install runtime dependencies
sudo apt install --yes --no-install-recommends \
netcat-openbsd \
ca-certificates \
ghostscript \
fonts-liberation \
fonts-dejavu \

Next, download source codes and extract.

export GUAC_VERSION="1.3.0"
curl -fLO "${GUAC_VERSION}/source/guacamole-server-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-server-${GUAC_VERSION}.tar.gz"

Configure the build and start the build.

cd "guacamole-server-${GUAC_VERSION}"
./configure --with-init-dir=/etc/init.d

Once done, install and start the service.

sudo make install
sudo ldconfig
sudo systemctl daemon-reload
sudo systemctl start guacd
sudo systemctl enable guacd

At this point, the Guacamole server (guacd) service should be up and running. Inspect by executing systemctl status guacd --no-pager.

Guacamole server (guacd) service status captured by author

Install Guacamole Client

Unlike the server, we don’t need to build it (but you can if you want). So we will download the WAR file and install on Tomcat server then expose it through nginx via HTTPS.

Install Tomcat

First, we need Tomcat to run the WAR file. Use this script to install it.

export TOMCAT_VERSION="8.5.65"
TOMCAT_MAJOR_VERSION=$(echo ${TOMCAT_VERSION} | awk -F . '{print $1}')
sudo apt-get install --yes default-jdk
sudo groupadd tomcat
sudo useradd -s /bin/false -g tomcat -d /opt/tomcat tomcat
sudo usermod -a -G tomcat "$USER"
curl -LO "${TOMCAT_MAJOR_VERSION}/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz"
sudo mkdir -p /opt/tomcat
sudo tar -xzf "apache-tomcat-${TOMCAT_VERSION}.tar.gz" -C /opt/tomcat --strip-components=1
sudo chgrp -R tomcat /opt/tomcat
cd /opt/tomcat
sudo chmod -R g+r conf
sudo chmod g+x conf
sudo chown -R tomcat webapps/ work/ temp/ logs/

Configure Tomcat and start the service.

JAVA_ALT_TEXT="$(update-java-alternatives -l || true)"
JAVA_HOME="$(echo "${JAVA_ALT_TEXT}" | awk '{print $3}')"
echo "[Unit]
Description=Apache Tomcat Web Application Container


Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'



[Install]" | sudo tee /etc/systemd/system/tomcat.service > /dev/null
sudo systemctl daemon-reload
sudo systemctl start tomcat
sudo systemctl enable tomcat

Now the Tomcat service should be up and running. Inspect by executing systemctl status tomcat.service --no-pager.

Tomcat service status captured by author

Add Guacamole Client Servlet

Download and add the WAR file to the Tomcat.

curl -LO "${GUAC_VERSION}/binary/guacamole-${GUAC_VERSION}.war"
sudo cp "guacamole-${GUAC_VERSION}.war" "/opt/tomcat/webapps/ROOT.war"
sudo chown tomcat:tomcat "/opt/tomcat/webapps/ROOT.war"
sudo rm -rf /opt/tomcat/webapps/ROOT

You may try to access the client at http://<your-server>:8080/ by either open or forward the port and you should see the Guacamole's login screen (but you can't login now).

Guacamole login screen captured by author

In case you see Tomcat instead of Guacamole, restart Tomcat using command sudo systemctl restart tomcat and try again.

Install nginx and certbot

We will use nginx as a proxy and certbot to get a certificate from Let’s Encrypt.

sudo apt install --yes nginx-core
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot

Configure certbot with a domain and an email address and integrate with nginx.

export DOMAIN_NAME="<Your VM FQDN>"
export EMAIL="<Your Email Address>"
sudo certbot --nginx -d "${DOMAIN_NAME}" -m "${EMAIL}" --agree-tos -n

Edit the file /etc/nginx/sites-enabled/default and replace the following section:

server_name your.server.fqdn; # managed by Certbot

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;

with this one:

server_name your.server.fqdn; # managed by Certbot

location / {

proxy_pass http://localhost:8080/;
proxy_buffering off;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_cookie_path /guacamole/ /;
access_log off;

This will set nginx as a proxy to your servlet. You don’t need to configure to redirect HTTP to HTTPS as that is already done by certbot.

Test the configuration and restart nginx.

sudo nginx -t
sudo systemctl restart nginx

Now you should be able to access Guacamole ay https://<your-server>/

Configure User and Connections

We will create a user with two connections in the user-mapping.xml file so we can test logging on.

Create the file /etc/guacamole/user-mapping.xml an put below content. Create the directory /etc/guacamole first if it does not exist.

<authorize username="guacadmin" password="guacadmin">
<connection name="this-server-ssh">
<param name="hostname">localhost</param>
<param name="port">22</param>
<connection name="some-win10-rdp">
<param name="hostname">vm-win10</param>
<param name="port">3389</param>
<param name="username">username</param>
<param name="password">thisisyourpassword</param>
<param name="ignore-cert">true</param>

The first connection is SSH to local server itself while the second one is RDP to Windows 10 VM. Don’t forget to update the username and password.

Restart Tomcat and test logging in and making the connections.

sudo systemctl restart tomcat
Guacamole home screen captured by author

If everything is configured properly, you should be able to connect to your VMs via either SSH and RDP. However, you cannot change anything on the web GUI the configuration is static in the file user-mapping.xml

Guacamole settings screen captured by author

To make it editable via the web GUI, we need to install a database. In this example, I will use MySQL but Guacamole also supports other databases e.g. MariaDB, PostgreSQL. You can check for more information in this documentation page.

Install MySQL

Let’s install MySQL.

sudo apt install --yes mysql-server

Check MySQL service status by executing systemctl status mysql --no-pager.

MySQL service status captured by author

After install the MySQL, we also need to install the Guacamole extension and library so it knows how to talk to the database.

# Download and install JDBC extensions
curl -fLO "${GUAC_VERSION}/binary/guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-auth-jdbc-${GUAC_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/extensions
sudo cp "guacamole-auth-jdbc-${GUAC_VERSION}/mysql/guacamole-auth-jdbc-mysql-${GUAC_VERSION}.jar" "/etc/guacamole/extensions/"

# Download and install MySQL Connector/J
curl -fLO "${CONNECTORJ_VERSION}.tar.gz"
tar -xvf "mysql-connector-java-${CONNECTORJ_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/lib
sudo cp "mysql-connector-java-${CONNECTORJ_VERSION}/mysql-connector-java-${CONNECTORJ_VERSION}.jar" "/etc/guacamole/lib/"

Configure Database

Next, we need to create a database and a user. In this example, I will create the database named guacamole_db and the user named guacamole_user. Please note the password must meet complexity criteria.

sudo mysql --execute='CREATE DATABASE guacamole_db;'
sudo mysql --execute="CREATE USER 'guacamole_user'@'localhost' IDENTIFIED BY '${MYSQL_PASSWORD}';"
sudo mysql --execute="GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO 'guacamole_user'@'localhost';"
sudo mysql --execute='FLUSH PRIVILEGES;'

Then run the provides scripts to create schema and the Guacamole default user guacadmin.

cat guacamole-auth-jdbc-1.3.0/mysql/schema/*.sql | sudo mysql guacamole_db

Lastly, you need to configure Guacamole client so it can connect to the database by creating file /etc/guacamole/ and put the following content.

# MySQL properties
mysql-hostname: localhost
mysql-port: 3306
mysql-database: guacamole_db
mysql-username: guacamole_user
mysql-password: <YourPasswordHere>

If you configure the user-mapping.xml file before then you may no longer need that so remove it.

sudo rm -f /etc/guacamole/user-mapping.xml

Restart Tomcat and test logging in again using default username and password i.e. guacadmin.

sudo systemctl restart tomcat

Now, you should be able to make changes in the Settings. It is recommended you create a new user and then remove the guacadmin as soon as possible.

Guacamole settings screen captured by author

Enable Two-Factor Authentication

If you want you can optionally install TOTP module to enable two-factor authentication to add more security to your VM pool. Just download the extension and restart Tomcat.

# Download and install TOTP extension
curl -fLO "${GUAC_VERSION}/binary/guacamole-auth-totp-${GUAC_VERSION}.tar.gz"
tar -xzf "guacamole-auth-totp-${GUAC_VERSION}.tar.gz"
sudo mkdir -p /etc/guacamole/extensions
sudo cp "guacamole-auth-totp-${GUAC_VERSION}/guacamole-auth-totp-${GUAC_VERSION}.jar" "/etc/guacamole/extensions/"

# Restart tomcat
sudo systemctl restart tomcat

Now, when you try logging in again, it will ask you to setup an authenticator and require you to input in every time you log in.

Multi-factor authentication setup screen captured by author


If you don’t want to perform all above steps one by one, you may leverage bash scripts I created in this repository.

Use at your own risk!

Originally published at on April 30, 2021.



Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store