Perfect π. Since you now have 6 fresh Ubuntu 24 VMs, letβs prepare them step by step so you get:
- Nginx installed
- PHP 8.3 installed
- Domain cayana.me pointing to VMs (via Cloudflare DNS)
- GitHub repo sync β CI/CD deploy to
/var/www/cayana.me - Dummy HTML page (to test across all)
- Later β load balancer logic
ssh azureuser@<VM_PUBLIC_IP>(replace <VM_PUBLIC_IP> with Kitkat/Dairymilk/Exoticdarkβs public IPs; private-only ones you reach through their sibling public VMs or Azure Bastion/SSH proxy).
Run on all 6 VMs:
sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx git unzip curl software-properties-commonsudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.3 php8.3-fpm php8.3-cli php8.3-common php8.3-mysql php8.3-curl php8.3-xml php8.3-mbstringEnable PHP-FPM:
sudo systemctl enable --now php8.3-fpmCheck it:
php -vsudo mkdir -p /var/www/cayana.me
sudo chown -R $USER:$USER /var/www/cayana.meDummy HTML for testing:
echo "<h1>Hello from $(hostname) at cayana.me</h1>" | sudo tee /var/www/cayana.me/index.htmlNow create Nginx site config:
sudo nano /etc/nginx/sites-available/cayana.mePaste:
server {
listen 80;
server_name cayana.me www.cayana.me;
root /var/www/cayana.me;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}Enable & reload:
sudo ln -s /etc/nginx/sites-available/cayana.me /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx- In Cloudflare, go to DNS for
cayana.me. - Add A records for:
cayana.me β <Kitkat Public IP>
cayana.me β <Dairymilk Public IP>
cayana.me β <Exoticdark Public IP>
(leave Barone, 5-Star, Kisses private β theyβll join cluster later).
That gives DNS round robin across 3 accounts.
On each VM:
cd /var/www/cayana.me
git init
git remote add origin https://github.com/<your-username>/<your-repo>.git
git pull origin mainTo auto-deploy on new pushes, use a GitHub webhook + small script.
Create script:
sudo nano /var/www/deploy.sh#!/bin/bash
cd /var/www/cayana.me || exit
git fetch --all
git reset --hard origin/mainMake executable:
sudo chmod +x /var/www/deploy.shNow setup a webhook in your repo β pointing to VM (http://<vm-ip>/webhook) OR use GitHub Actions to SSH deploy.
-
Visit
http://cayana.meβ should load from random VM. Each VM prints hostname β youβll see e.g.:Hello from kitkat-vm at cayana.meHello from dairymilk-vm at cayana.me- etc.
-
PHP test:
echo "<?php phpinfo(); ?>" | sudo tee /var/www/cayana.me/info.phpVisit http://cayana.me/info.php β should show PHP 8.3 page.
π At this stage:
- You have working site across 6 VMs (3 public, 3 private).
- DNS is set (Cloudflare Free).
- GitHub repo is synced (manual pull for now, or CI/CD later).
- PHP 8.3 + Nginx ready.
β‘ Next step: I can help you wire the cluster load-aware redirect/proxy so private VMs also serve traffic through public ones.
Got it π thanks for sharing the error details.
Youβre seeing ERR_CONNECTION_REFUSED when opening https://cayana.me. Letβs troubleshoot step by step:
This usually means:
- The server is not listening on the requested port (443 for HTTPS).
- A firewall / security group is blocking access.
- Nginx is running but not configured for SSL, so itβs rejecting HTTPS traffic.
Run:
dig cayana.me +shortor
nslookup cayana.meβ Make sure it resolves to the public IP of your VPS.
On the VPS:
sudo systemctl status nginxIf itβs inactive, start it:
sudo systemctl start nginx
sudo systemctl enable nginxsudo ss -tlnp | grep nginxYou should see something like:
LISTEN 0 128 *:80 nginx
LISTEN 0 128 *:443 nginx
π If port 443 is missing, Nginx isnβt serving HTTPS.
On Ubuntu with UFW:
sudo ufw statusMake sure you allow:
sudo ufw allow 80
sudo ufw allow 443If youβre on cloud VPS (AWS Lightsail, Azure, etc.), also check the providerβs security group rules.
If you only have a test setup and didnβt install SSL yet, Nginx is likely refusing HTTPS. You have two quick options:
Edit your Nginx config:
sudo nano /etc/nginx/sites-available/defaultAdd:
server {
listen 80;
server_name cayana.me www.cayana.me;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}Then:
sudo nginx -t
sudo systemctl reload nginxβ
Now test http://cayana.me.
Install certbot:
sudo apt install -y certbot python3-certbot-nginxRun:
sudo certbot --nginx -d cayana.me -d www.cayana.meThis will:
- Get a free SSL cert
- Update Nginx config
- Enable HTTPS automatically
Then reload Nginx:
sudo systemctl reload nginxhttp://cayana.meshould work if you did Option A.https://cayana.meshould work if you did Option B.
π Based on your note βmaybe nginx is not having ssl is problem as only https is onβ β yes, exactly. If you only have an A record but no SSL certificate & Nginx HTTPS config, the browser refuses HTTPS.
Would you like me to prepare a ready-to-use minimal Nginx config for cayana.me (with or without SSL), so you can just paste it into /etc/nginx/sites-available/ and enable it?
Perfect question π β if youβre using Cloudflare, you donβt need Letβs Encrypt; instead, you can install a Cloudflare Origin Certificate on your VPS.
This is often better because:
- The certificate is issued by Cloudflare and only trusted between your VPS β Cloudflare edge servers.
- Visitors see Cloudflareβs SSL (the public one).
- You can enable βFull (Strict)β SSL mode in Cloudflare for best security.
-
Log in to Cloudflare Dashboard β Select your domain (
cayana.me). -
Go to SSL/TLS β Origin Server.
-
Click Create Certificate.
-
Choose:
- Private key type: RSA (2048).
- Hosts:
cayana.meand*.cayana.me. - Validity: up to 15 years.
-
Cloudflare will generate:
- An Origin Certificate (looks like
-----BEGIN CERTIFICATE-----). - A Private Key (save it, you wonβt see it again).
- An Origin Certificate (looks like
On your VPS (Linux):
sudo mkdir -p /etc/ssl/certs/cloudflare
sudo nano /etc/ssl/certs/cloudflare/cayana.me.pemPaste the Origin Certificate here.
Then create the private key file:
sudo nano /etc/ssl/certs/cloudflare/cayana.me.keyPaste the Private Key here.
Secure permissions:
sudo chmod 600 /etc/ssl/certs/cloudflare/cayana.me.*Edit your site config:
sudo nano /etc/nginx/sites-available/cayana.meExample config:
server {
listen 80;
server_name cayana.me www.cayana.me;
# Redirect HTTP -> HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name cayana.me www.cayana.me;
ssl_certificate /etc/ssl/certs/cloudflare/cayana.me.pem;
ssl_certificate_key /etc/ssl/certs/cloudflare/cayana.me.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}Enable site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/cayana.me /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx- Go to Cloudflare Dashboard β SSL/TLS β Overview.
- Select Full (Strict) mode.
This ensures:
- Cloudflare β Client = Cloudflareβs cert (trusted).
- Cloudflare β Origin VPS = Origin Cert (yours).
Check from local machine:
curl -I https://cayana.meYou should see a 200 OK (or your pageβs status).
β Now, your site is:
- Securely encrypted end-to-end.
- Protected by Cloudflare SSL in front.
- Using Cloudflare Origin Certificate at your VPS.
Do you want me to also write a ready-to-paste Nginx config with Cloudflareβs recommended SSL hardening (HSTS, OCSP stapling, ciphers) so you get an A+ rating on SSL Labs?
Thanks for pasting the error β this tells us exactly whatβs wrong.
The key error is:
SSL_CTX_set_cipher_list(...) failed (SSL: error:0A000118:SSL routines::invalid command)
That happens when:
- Your Nginx build (or linked OpenSSL library) doesnβt recognize some cipher strings I gave.
- Different distros ship Nginx with different SSL modules, so certain cipher lists fail.
Weβll simplify the SSL section so itβs compatible across most Linux distros:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers HIGH:!aNULL:!MD5;This keeps it secure but avoids hardcoding unsupported ciphers.
Your /etc/nginx/sites-available/cayana.me should look like this:
server {
listen 80;
server_name cayana.me www.cayana.me;
# Redirect HTTP -> HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name cayana.me www.cayana.me;
root /var/www/cayana.me;
index index.php index.html;
ssl_certificate /etc/ssl/certs/cloudflare/cayana.me.pem;
ssl_certificate_key /etc/ssl/certs/cloudflare/cayana.me.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
# Optional: enable HSTS (be careful in testing!)
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}- Test config:
sudo nginx -t- If no errors:
sudo systemctl restart nginxThat message just means you already had a symlink from
/etc/nginx/sites-enabled/cayana.me β /etc/nginx/sites-available/cayana.me.
No problem β itβs already enabled, so you donβt need to re-link.
After this fix, nginx -t should succeed and https://cayana.me should load through Cloudflare.
π Do you want me to also add OCSP stapling & auto security headers (X-Frame-Options, Referrer-Policy, etc.) to harden your Nginx further, or keep it minimal for now?
Nice! π Now letβs make your repo directly mapped to /var/www/cayana.me so you can update your site with one simple git pull or even push directly from GitHub β VM.
cd /var/www/cayana.me
sudo rm -rf .git # in case any leftover
git init
git remote add origin https://github.com/<your-username>/<your-repo>.git
git fetch origin main
git checkout -t origin/mainNow /var/www/cayana.me = your repo.
sudo chown -R $USER:$USER /var/www/cayana.meCreate deploy script:
sudo nano /usr/local/bin/deploy-cayana.shPaste:
#!/bin/bash
cd /var/www/cayana.me || exit
git fetch --all
git reset --hard origin/main
echo "β
cayana.me updated at $(date)"Make executable:
sudo chmod +x /usr/local/bin/deploy-cayana.shNow anytime you want to update:
deploy-cayana.shIf you want to make edits on the VM itself:
cd /var/www/cayana.me
git add .
git commit -m "Update from VM"
git push origin mainπ For this to work, youβll need to set up SSH deploy key or use HTTPS with a GitHub Personal Access Token.
If you want automatic updates whenever you push to GitHub:
- Go to GitHub Repo β Settings β Webhooks
- Add webhook β
http://<VM_PUBLIC_IP>/webhook
On VM, set up a tiny webhook listener (Python/Node or PHP) that just runs deploy-cayana.sh.
π₯ Result:
/var/www/cayana.meis now your repodeploy-cayana.shgives one-click pull- You can push changes back if you want
Do you want me to set this up push-only from GitHub (CI/CD style), or keep it manual one-click pull for now?
Exactly π β but only if you set up the webhook listener on your VM.
Right now with just deploy-cayana.sh, you need to manually run it to pull changes.
To make it automatic whenever you push to GitHub, you need:
- On GitHub β Actions Workflow that runs every push to
main. - The workflow connects to your VM via SSH and runs
deploy-cayana.sh. β Super reliable, no need to open ports on VM. β‘ Recommended.
-
Add a webhook in GitHub repo:
http://<your-vm-public-ip>/webhook(event: push)
-
On VM, run a tiny service (Node.js, Python Flask, or PHP) that listens on
/webhookand executesdeploy-cayana.sh. Example with Python (Flask):pip install flask
# /opt/webhook/webhook.py from flask import Flask, request import subprocess app = Flask(__name__) @app.route('/webhook', methods=['POST']) def webhook(): subprocess.Popen(["/usr/local/bin/deploy-cayana.sh"]) return "OK", 200 if __name__ == '__main__': app.run(host="0.0.0.0", port=80)
Then run it as a service (so it auto-starts).
-
Just set a cronjob to auto-pull every 1 minute (or 5):
crontab -e
Add:
* * * * * /usr/local/bin/deploy-cayana.sh >> /var/log/deploy.log 2>&1β Super simple β Not instant β updates apply on schedule (every minute).
π If you want real-time auto-deploy β go with GitHub Actions (Option 1). π If you want no GitHub config, just VM side β cronjob (Option 3).
Do you want me to write you the GitHub Actions workflow for Option 1 (best way), so your VM always updates instantly on push?
Got it π β since your repo is now synced to /var/www/cayana.me, we donβt need that old index.html we manually created.
Run this on each VM where you created the dummy file:
sudo rm -f /var/www/cayana.me/index.htmlπ Then confirm the repo content is being served instead:
ls -l /var/www/cayana.meYou should see your GitHub repo files there.
Would you like me to also adjust your Nginx config so it serves /var/www/cayana.me/public (in case your repo has a public/ folder, like many PHP/Laravel setups), or should it directly serve the repo root?
Ah got it π you mean the script you made earlier (deploy-cayana.sh) inside /usr/local/bin.
To run it manually, just SSH into your VM and run:
deploy-cayana.shor explicitly:
bash /usr/local/bin/deploy-cayana.sh- Goes to
/var/www/cayana.me - Fetches the latest changes from your GitHub repo
- Resets files to match
origin/main - Prints a log line like:
β cayana.me updated at Thu Sep 12 15:32:00 UTC 2025
-
Manual update Run
deploy-cayana.shanytime you want to pull the latest repo changes. -
Cronjob (already set up) Runs automatically every minute via your crontab:
* * * * * /usr/local/bin/deploy-cayana.sh >> /var/log/deploy.log 2>&1
You can check logs with:
tail -f /var/log/deploy.log
-
GitHub Actions / Webhook (optional) If you want instant deployment (not every 1 min), you can make GitHub trigger this script via SSH or webhook.
π Do you want me to write you a GitHub Actions workflow so every time you push to your repo, GitHub automatically SSHes into your VM and runs deploy-cayana.sh?
Got it π right now your cron runs every 1 minute. To change it to every 5 minutes, edit the crontab:
crontab -eThen replace this line:
* * * * * /usr/local/bin/deploy-cayana.sh >> /var/log/deploy.log 2>&1with:
*/5 * * * * /usr/local/bin/deploy-cayana.sh >> /var/log/deploy.log 2>&1β
Now deploy-cayana.sh will run every 5 minutes.
You can confirm itβs loaded by checking:
crontab -lAnd watch the logs:
tail -f /var/log/deploy.logWant me to also set up a manual trigger alias (like update-site command) so you can force an update instantly without waiting for cron?
Ah, yes β the /load script idea π₯
Hereβs how you can implement it step by step so each VM knows the cluster load and forwards new users to the least-loaded VM:
On all 6 VMs:
sudo apt install -y jq curlWeβll use a small PHP script served by Nginx. Create the file:
sudo nano /var/www/cayana.me/load.phpPaste this:
<?php
// Basic load metric
$load = sys_getloadavg(); // 1, 5, 15 min load averages
$connections = (int) exec("netstat -an | grep ESTABLISHED | wc -l");
$data = [
"vm" => gethostname(),
"load1" => $load[0],
"connections" => $connections,
"timestamp" => time()
];
header('Content-Type: application/json');
echo json_encode($data);
?>β
Now visiting http://<vm-ip>/load.php will return something like:
{
"vm": "kitkat-vm",
"load1": 0.42,
"connections": 12,
"timestamp": 1694567890
}If you prefer not to expose PHP, you can let Nginx serve /load as a reverse proxy, but for simplicity PHP is fine.
Now modify each VMβs main Nginx site to have a small PHP front controller that:
- On each new request, it queries all 6 VMsβ
/load.php. - Chooses the VM with the lowest load.
- Redirects the user there (302).
Example:
<?php
$nodes = [
"http://KITKAT_PUBLIC_IP/load.php",
"http://DAIRYMILK_PUBLIC_IP/load.php",
"http://EXOTICDARK_PUBLIC_IP/load.php",
"http://10.1.0.5/load.php", // Barone private IP
"http://10.2.0.5/load.php", // 5-Star private IP
"http://10.3.0.5/load.php" // Kisses private IP
];
$best = null;
$lowest = PHP_FLOAT_MAX;
foreach ($nodes as $url) {
$resp = @file_get_contents($url);
if ($resp) {
$data = json_decode($resp, true);
if ($data && $data['load1'] < $lowest) {
$lowest = $data['load1'];
$best = $url;
}
}
}
if ($best) {
// Strip /load.php β root of site
$best = str_replace("/load.php", "/", $best);
header("Location: $best");
exit;
}
echo "All nodes unreachable. Please try later.";Save as:
sudo nano /var/www/cayana.me/index.php- Cloudflare DNS β RR to
kitkat,dairymilk,exoticdark - User lands on one of those
- That VM runs the script β checks cluster load β redirects new user to the least loaded VM (public or private)
- Existing users stay where they landed (so sessions not broken)
- Cache results in
/tmp/load.jsonfor ~5s to avoid excessive inter-VM calls. - Use Nginx
luainstead of PHP for lower latency. - Secure
/load.phpwith a token so outsiders canβt scrape your VM load.
π Do you want me to make this transparent reverse-proxy (user doesnβt see redirect, VM internally forwards traffic), or keep the 302 redirect version (simpler but userβs URL changes)?
Would you prefer me to design the redirect-based model (302) first, or the transparent reverse-proxy model (user never leaves VM, but traffic is forwarded to least-loaded one)?