Automatic agency subdomains

Do this once. After it, every approved agency gets name.quorlasocial.com with HTTPS automatically, without anyone touching Hestia.

Server: 72.62.169.43 (HestiaCP user user) Run as: dev, with sudo where shown Time: about 30 minutes

What this changes. Today a new agency's workspace (container, data, port and welcome email) is created by the provisioning script, but its web address needs three manual steps in Hestia: DNS, SSL and nginx. This setup replaces those with:

Nothing changes for app.quorlasocial.com or clickstocart.quorlasocial.com: nginx always prefers an exact server name over the wildcard.

If any command's output looks different from what this page expects, stop and paste the output to Claude before going on.

0Check and back up

sudo nginx -t
nginx -v
sudo /usr/local/hestia/bin/v-list-web-domain user quorlasocial.com
sudo /usr/local/hestia/bin/v-list-dns-records user quorlasocial.com | grep -E '^ID|\*'
which nginx systemctl
sudo tar -czf ~/nginx-backup-$(date +%F).tar.gz /etc/nginx
Expect:
  • nginx -t says the syntax is ok.
  • The web-domain line tells you whether quorlasocial.com is a web domain on Hestia. Note the answer, because it decides step 2.
  • The DNS line tells you whether a * record already exists.
  • Note the two paths from which, usually /usr/sbin/nginx and /usr/bin/systemctl.

1Wildcard DNS

Every anything.quorlasocial.com then points at this server. Skip this step if step 0 already showed a * record.

sudo /usr/local/hestia/bin/v-add-dns-record user quorlasocial.com '*' A 72.62.169.43
dig +short test123.quorlasocial.com @8.8.8.8
Expect: 72.62.169.43. It can take a few minutes to appear.

2Wildcard SSL certificate

Hestia can issue a *.quorlasocial.com certificate because the DNS zone is on this server, and it renews the certificate by itself.

Only if step 0 showed that quorlasocial.com is not a web domain yet, add it first. Do this only if the bare domain isn't hosted somewhere else (for example a marketing site); if it is, ask Claude first.

sudo /usr/local/hestia/bin/v-add-web-domain user quorlasocial.com

Then add the wildcard alias and issue the certificate:

sudo /usr/local/hestia/bin/v-add-web-domain-alias user quorlasocial.com '*.quorlasocial.com'
sudo /usr/local/hestia/bin/v-add-letsencrypt-domain user quorlasocial.com '*.quorlasocial.com'
sudo openssl x509 -in /home/user/conf/web/quorlasocial.com/ssl/quorlasocial.com.pem -noout -subject -ext subjectAltName -enddate
Expect: the certificate lists DNS:*.quorlasocial.com and an end date about 90 days away.

3One nginx rule for every agency

This file lives outside Hestia's per-domain folders, so Hestia rebuilds don't overwrite it. It looks up the agency's port by the subdomain. Addresses that aren't in the map get a 404.

touch /home/dev/APPS/quorla-tenants/nginx-tenants.map
sudo tee /etc/nginx/conf.d/quorla-tenants.conf >/dev/null <<'EOF'
map $host $quorla_tenant {
  default "";
  include /home/dev/APPS/quorla-tenants/nginx-tenants.map;
}
server {
  listen 72.62.169.43:80;
  server_name *.quorlasocial.com;
  return 301 https://$host$request_uri;
}
server {
  listen 72.62.169.43:443 ssl;
  server_name *.quorlasocial.com;
  ssl_certificate     /home/user/conf/web/quorlasocial.com/ssl/quorlasocial.com.pem;
  ssl_certificate_key /home/user/conf/web/quorlasocial.com/ssl/quorlasocial.com.key;
  client_max_body_size 500m;
  proxy_read_timeout 600s;
  location / {
    if ($quorla_tenant = "") { return 404; }
    proxy_pass http://$quorla_tenant;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}
EOF
sudo nginx -t && sudo systemctl reload nginx
Expect: syntax is ok. If nginx warns that *.quorlasocial.com is a "conflicting server name", the step 2 alias made Hestia's quorlasocial.com config claim the wildcard too. Paste the warning to Claude; the fix is a one-line change.

4Let the script reload nginx

The script runs as dev. It needs exactly two root commands and nothing else. Use the paths from step 0 if they differ.

echo 'dev ALL=(root) NOPASSWD: /usr/sbin/nginx -t, /usr/bin/systemctl reload nginx' | sudo tee /etc/sudoers.d/quorla
sudo chmod 440 /etc/sudoers.d/quorla
sudo visudo -c
sudo -n nginx -t && echo "sudo rule works"
Expect: parsed OK, then sudo rule works, without being asked for a password.

5Run the script every 5 minutes

It picks up approved agencies, creates their workspaces, moves agencies onto new versions after a deploy, and refreshes the nginx map.

bash /home/dev/APPS/quorlasocial/deploy/provision-tenants.sh
cat /home/dev/APPS/quorla-tenants/nginx-tenants.map
( crontab -l 2>/dev/null | grep -v provision-tenants.sh; echo '*/5 * * * * /home/dev/APPS/quorlasocial/deploy/provision-tenants.sh >> /home/dev/APPS/quorla-tenants/provision.log 2>&1' ) | crontab -
crontab -l
Expect: the map contains clickstocart.quorlasocial.com 127.0.0.1:4400;, the script logs nginx reloaded the first time, and the cron line is listed.

6Test

curl -sI https://app.quorlasocial.com | head -1
curl -sI https://clickstocart.quorlasocial.com | head -1
curl -sI https://nobody-here.quorlasocial.com | head -1
Expect: the first two return 200 (or a redirect), and the last returns 404 with a valid certificate (no SSL error).

Then do a real end-to-end test:

Undo

sudo rm /etc/nginx/conf.d/quorla-tenants.conf
sudo nginx -t && sudo systemctl reload nginx
crontab -l | grep -v provision-tenants.sh | crontab -

The DNS record and the certificate are harmless to leave in place. Existing workspaces keep running either way.