Congrats, CloudStack is online, chef’s kiss. Now we add the armor and ship a version your future self will thank you for, not hate you for 🤣
here’s what we’ll tackle in this part:
- Turn on UEFI with Secure Boot support.
- Put Nginx in front as our load balancer/reverse proxy, terminate TLS, and (optionally) pair it with Cloudflare for bot/DDoS protection.
- Custom brand CloudStack (UI polish, console proxy URL, secondary storage domains) so it feels like your cloud.
- Launch a fresh VM end-to-end, with a couple of gotchas called out so you don’t trip on them.
Quick note on Cloudflare: totally your call. I use it because it makes WAF rules and caching brain-dead simple 😂 and it gives me free SSL. if you’d rather keep it lean, skip it. We’ll still lock things down with Nginx and sane firewall rules.
About the web serving: CloudStack’s system VMs (console proxy and secondary storage) ship with Apache under the hood. we’re not ripping that out. instead, we’ll put Nginx at the edge to handle the UI and forward only what’s needed to the system VMs. the goal is to mask the System VMs and UI’s IPs and present a single, clean entry point for users and tools. it’s tidier, safer, and easier to maintain.
Launch first VM
I added a gallery instead of individual images with steps to keep things short. Please note that CloudStack’s ready made templates do not come with Windows images.
What are the steps to follow here:
- Head to Service Offerings – Compute Offering
The default are small offerings, you can create new ones and customize as you wish or create custom offering so you won’t have to create multiple offerings. - Head to Compute – Instances or Top right near the user Icon (Create – Instance)
Create a new VM, Select the template and Compute Offering, Storage, Network and Continue. You can operate instance by press “View console” or using SSH.
Before using SSH for VM’s, You need to allow egress/ingress ports in security group under Network tab in the Menu or under Security groups in the VM’s Page. If there are any errors during Creation of the VM. Check my Github page for faqs.
Setting Up UEFI
The host system must be installed in UEFI mode.
You can verify the current boot mode
test -d /proc/device-tree && echo "U-Boot mode" || (test -d /sys/firmware/efi && echo "UEFI boot mode" || echo "Legacy BIOS boot")
Configure QEMU by editing the qemu.conf
file
nano /etc/libvirt/qemu.conf
Find and uncomment, change or append the following to the configuration
nvram = [
"/usr/share/OVMF/OVMF_CODE_4M.fd:/usr/share/OVMF/OVMF_VARS_4M.fd",
"/usr/share/OVMF/OVMF_CODE_4M.secboot.fd:/usr/share/OVMF/OVMF_VARS_4M.fd",
"/usr/share/OVMF/OVMF_CODE_4M.ms.fd:/usr/share/OVMF/OVMF_VARS_4M.ms.fd"
]
UEFI related params information added in uefi.properties
which is located /etc/cloudstack/agent
nano /etc/cloudstack/agent/uefi.properties
Paste the specified content into the uefi.properties
file
guest.nvram.template.secure=/usr/share/OVMF/OVMF_VARS_4M.ms.fd
guest.loader.secure=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd
guest.nvram.template.legacy=/usr/share/OVMF/OVMF_VARS_4M.fd
guest.loader.legacy=/usr/share/OVMF/OVMF_CODE_4M.fd
guest.nvram.path=/var/lib/libvirt/qemu/nvram/
Restart the service
systemctl restart libvirtd cloudstack-agent cloudstack-management
Great work, Now go to the UI.
- Click on “Infrastructure” on the left side
- Click on “Host” and click on the host in use
- Find “UEFI supported” below the “Details” section; if it shows true, it means the setup was successful
When creating an “instance”, enable “Advanced” mode and select “UEFI” as the “Boot type”
Setup NGINX + SSL + Cloudflare
Let’s go ahead and Secure our frontend with NGINX, SSL, Cloudflare (Or provider of your choice).
This step is only useful if you are planning on using a domain name to access the UI, Console, Download Storage Images and Mask the origin IP, Setup WAF Rules, CDN, etc…
Putting Cloudflare (Or any loadbalancer) in front to hide the origin IP buys real security and resilience, even with caching off. Cloudflare sits as a reverse proxy at the edge, absorbing junk traffic, terminating TLS, and filtering requests before they ever touch your setup. That alone cuts down scans, L7 attacks, and TLS-exhaustion attempts that would otherwise hammer your setup.
Settings
Go to Infrastructure – System VMs and look for the IP’s of the System VM’s
The System VM’s should be running and the agent is up.
Next, Click on “Configuration” on the left side, then click on “Global Settings”
- Search for consoleproxy.url.domain and secstorage.ssl.cert.domain and change accordingly.
- Search for consoleproxy.sslEnabled and secstorage.encrypt.copy and set to true/toggle on.
Install Certbot
In order for us to enable SSL for the CloudStack UI, CPVM, and SSVM, we’ll set up Nginx as a reverse proxy. For certificates, we’ll use Certbot (Let’s Encrypt)—it’s free, quick, and auto-renews before the 90-day expiry.
Setup Certbot
In this Instance, I’m using Certbot with Cloudflare’s DNS challenge to issue the TLS cert. Grab a scoped Cloudflare API Token here, plug it into Certbot’s Cloudflare plugin, and you’ll get hands-free renewals. Certbot supports plenty of other DNS providers, pick the one that matches yours, Check here
sudo apt install -y certbot python3-certbot-dns-cloudflare
sudo mkdir -p /etc/letsencrypt
echo "dns_cloudflare_api_token = YOUR_API_TOKEN_HERE" > /root/.cloudflare.ini
sudo chmod 600 /root/.cloudflare.ini
# Issue a Certificate
certbot certonly -d example.com -d "*.example.com" \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.cloudflare.ini \
--key-type rsa \
--rsa-key-size 2048 \
--agree-tos \
--force-renewal\
--no-eff-email\
--email YOUR@EMAIL.COM
# The cert output should be in /etc/letsencrypt/live/example.com/
Installing NGINX
I usually bump CloudStack’s default ports from 8080/8443 to 9080/9443 so Nginx and the Java/Tomcat services don’t fight over the same sockets.
First, we need to setup KeyStore for CloudStack
# Verify key and certificate match. MD5 'must' match. Else: Generate new ones
echo "Certificate modulus:"
openssl x509 -noout -modulus -in /etc/letsencrypt/live/example.com/cert.pem | openssl md5
echo "Private key modulus:"
openssl rsa -noout -modulus -in /etc/letsencrypt/live/example.com/privkey.pem | openssl md5
openssl pkcs12 -export -in /etc/letsencrypt/live/example.com/fullchain.pem \
-inkey /etc/letsencrypt/live/example.com/privkey.pem \
-out /etc/cloudstack/management/cloudstack.p12 \
-name cloudstack \
-password pass:STRONGPASSWORDHERE!
openssl pkcs12 -export \
-inkey /etc/letsencrypt/live/example.com/privkey.pem \
-in /etc/letsencrypt/live/example.com/fullchain.pem \
-name cloudstack \
-out /etc/cloudstack/management/cloud.pkcs12 \
-password pass:STRONGPASSWORDHERE!
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-in /etc/letsencrypt/live/example.com/privkey.pem \
-out /etc/cloudstack/management/privkey.pkcs8 -nocrypt
keytool -importkeystore \
-srckeystore /etc/cloudstack/management/cloudstack.p12 \
-srcstoretype PKCS12 \
-srcstorepass STRONGPASSWORDHERE! \
-destkeystore /etc/cloudstack/management/cloudstack.jks \
-deststoretype JKS \
-deststorepass STRONGPASSWORDHERE!
Setup Cloudmonkey
# Install snap, then Install cloudmonkey
sudo apt install -y snapd
sudo snap install cloudmonkey
cmk sync
cmk set profile mycloud
cmk set url http://127.0.0.1:8080
cmk set username YOUR_USERNAME
cmk set password YOUR_PASSWORD
cmk sync
# This is unsafe, We need to change them later to apikey secretkey instaed of username password and clear username, password.
# Don't forget to change url ass well after changing the port.
# You need to "wget https://letsencrypt.org/certs/isrgrootx1.pem" then combine it with chain.pem
cmk upload customcertificate id=1 name=LetsEncryptChain certificate="$(cat /etc/letsencrypt/live/example.com/isrgrootx1.pem)" domainsuffix='*.example.com'
cmk upload customcertificate id=2 certificate="$(cat /etc/letsencrypt/live/example.com/cert.pem)" privatekey="$(cat /etc/cloudstack/management/privkey.pkcs8)" domainsuffix='example.com'
cmk upload customcertificate id=4 certificate="$(cat /etc/letsencrypt/live/example.com/fullchain.pem)" privatekey="$(cat /etc/cloudstack/management/privkey.pkcs8)" domainsuffix='*.example.com'
nano /etc/cloudstack/management/server.properties
# Change 8080 port to 9080
http.enable=true
http.port=9080
# Set https to true and change port to 9443
https.enable=true
https.port=9443
# Change the following
https.keystore=/etc/cloudstack/management/cloudstack.jks
https.keystore.password=STRONGPASSWORDHERE!
# Use plain-text password
I have curated NGINX configs for exactly CloudStack and it’s services, on my Github
sudo apt install -y nginx nginx-extras nginx-common
cd /etc/nginx
# Copy and replace nginx.conf from github
wget https://github.com/abdessalllam/cloud-setup/raw/refs/heads/main/Nginx%20Confs/nginx.conf
# Then go to sites-available
cd /etc/nginx/sites-available
# Download UI, CPVM, SSVM Configs here.
# Adjust all "Important: Change this" lines
wget https://github.com/abdessalllam/cloud-setup/raw/refs/heads/main/Nginx%20Confs/sites-available/ui.conf
wget https://github.com/abdessalllam/cloud-setup/raw/refs/heads/main/Nginx%20Confs/sites-available/ssvm.conf
wget https://github.com/abdessalllam/cloud-setup/raw/refs/heads/main/Nginx%20Confs/sites-available/cpvm.conf
# Should have 3 Files
root@cloudstack:/etc/nginx/sites-available# ls -ls
total 12
4 -rw-r--r-- 1 root root 1976 Jan 14 01:38 cpvm.conf
4 -rw-r--r-- 1 root root 1006 Jan 14 01:40 ssvm.conf
4 -rw-r--r-- 1 root root 2096 Jan 14 01:45 ui.conf
root@cloudstack:/etc/nginx/sites-available#
# Enable the configs
sudo ln -s /etc/nginx/sites-available/ui.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/cpvm.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/ssvm.conf /etc/nginx/sites-enabled/
# Add Cloudflare Ranges "If in Use" if not modify configs to comment the include file for realip.conf
cd /etc/nginx/conf.d/
wget https://github.com/abdessalllam/cloud-setup/raw/refs/heads/main/Nginx%20Confs/conf.d/realip.conf
# Reload Nginx
sudo nginx -t
# If there are no erros
sudo systemctl reload nginx
systemctl restart cloudstack-management
Voilà—your CloudStack UI, console proxy, and secondary storage should now be reachable over your domain with TLS.
Next, hop into Cloudflare (or your CDN/WAF) and set no-cache rules for these hostnames. On Cloudflare specifically, add Cache/Page Rules (or Cache Rules) to bypass caching for the UI/console/storage domains, and disable Rocket Loader for them—it can interfere with the console.
One more heads-up: the UI is still reachable via the public IP. That’s not ideal. In the next part, we’ll lock down direct-to-origin access, encrypt database connections, and wire up iptables as a small, reliable service & Overcommit CPU/RAM/STORAGE.