# Custom Domains Setup Guide Complete guide to configuring custom domains with SSL certificates, DNS verification, and multi-tenant domain management. **Time Estimate**: 15-30 minutes (+ DNS propagation time) **Difficulty**: Intermediate **Prerequisites**: Domain registrar access, nself project running --- ## Table of Contents 1. [Overview](#overview) 2. [Single Domain Setup](#single-domain-setup) 3. [Multi-Tenant Domains](#multi-tenant-domains) 4. [SSL Certificate Management](#ssl-certificate-management) 5. [DNS Configuration](#dns-configuration) 6. [Wildcard Domains](#wildcard-domains) 7. [Domain Verification](#domain-verification) 8. [Production Deployment](#production-deployment) 9. [Troubleshooting](#troubleshooting) --- ## Overview ### What You Can Do - **Single domain**: `app.yourcompany.com` - **Multiple domains**: `app1.com`, `app2.com`, `app3.com` - **Wildcard domains**: `*.yourdomain.com` - **Multi-tenant**: Each client gets their own domain - **Automatic SSL**: Let's Encrypt with auto-renewal - **Custom SSL**: Upload your own certificates ### Domain Types **Apex domain** (root): ``` example.com ``` **Subdomain**: ``` app.example.com api.example.com ``` **Wildcard**: ``` *.example.com # Matches: app.example.com, api.example.com, anything.example.com ``` --- ## Single Domain Setup ### Scenario: Change from default to custom domain **Default**: `https://api.local.nself.org` **Goal**: `https://api.myapp.com` ### Step 1: Update Configuration (2 minutes) Edit `.env`: ```bash # Change from: BASE_DOMAIN=local.nself.org # To: BASE_DOMAIN=myapp.com ``` ### Step 2: Rebuild (1 minute) ```bash nself build ``` This regenerates: - `docker-compose.yml` - `nginx/` configurations - SSL certificates ### Step 3: Configure DNS (5 minutes) **At your domain registrar** (GoDaddy, Namecheap, Cloudflare, etc.): Add these DNS records: ``` Type Name Value TTL A @ your-server-ip 300 A * your-server-ip 300 ``` **Or use specific subdomains**: ``` Type Name Value TTL A api your-server-ip 300 A auth your-server-ip 300 A admin your-server-ip 300 ``` **For local development**: Edit `/etc/hosts` (macOS/Linux) or `C:\Windows\System32\drivers\etc\hosts` (Windows): ``` 127.0.0.1 api.myapp.local 127.0.0.1 auth.myapp.local 127.0.0.1 admin.myapp.local ``` ### Step 4: Provision SSL (3 minutes) **Self-signed** (development): ```bash nself ssl generate myapp.com --self-signed ``` **Let's Encrypt** (production): ```bash nself ssl generate myapp.com --letsencrypt ``` **Automatic renewal**: ```bash nself ssl enable-auto-renew myapp.com ``` ### Step 5: Restart Services (1 minute) ```bash nself restart nginx ``` ### Step 6: Verify (2 minutes) ```bash # Check DNS nslookup api.myapp.com # Check SSL curl -I https://api.myapp.com # List all URLs nself urls ``` **Expected output**: ``` ✓ DNS: api.myapp.com → your-server-ip ✓ SSL: Valid certificate ✓ Service: Running URLs: https://api.myapp.com (Hasura GraphQL) https://auth.myapp.com (Authentication) https://admin.myapp.com (Admin Dashboard) ``` --- ## Multi-Tenant Domains ### Scenario: Each client gets their own domain **Clients**: - Acme Corp: `app.acme.com` - TechCo: `app.techco.com` - StartupXYZ: `app.startupxyz.com` ### Step 1: Initialize White-Label System (2 minutes) ```bash nself whitelabel init ``` ### Step 2: Add Domain for Tenant (3 minutes per domain) **Tenant 1: Acme Corp** ```bash nself whitelabel domain add app.acme.com --tenant acme-corp ``` **Output**: ``` Domain added: app.acme.com Tenant: acme-corp Status: pending_verification Next steps: 1. Configure DNS (see below) 2. Run: nself whitelabel domain verify app.acme.com ``` **DNS Instructions**: ``` Add these records to acme.com DNS: Type Name Value TTL A app your-server-ip 300 TXT _verify.app "nself-verify-abc123def" 300 ``` ### Step 3: Client Configures DNS (5 minutes) **Instruct client to add**: ``` Type Name Value TTL A app your-server-ip 300 CNAME app yourplatform.com 300 # Alternative ``` ### Step 4: Verify DNS (2 minutes) **Wait for DNS propagation** (1-48 hours, usually 5-15 minutes) ```bash # Check DNS nslookup app.acme.com # Verify domain nself whitelabel domain verify app.acme.com ``` **Output**: ``` ✓ DNS propagated ✓ A record points to your-server-ip ✓ Domain verified Status: verified ``` ### Step 5: Provision SSL (3 minutes) **Automatic (Let's Encrypt)**: ```bash nself whitelabel domain ssl app.acme.com --auto-renew ``` **Manual (custom certificate)**: ```bash nself whitelabel domain ssl app.acme.com \ --cert /path/to/cert.pem \ --key /path/to/key.pem \ --chain /path/to/chain.pem ``` ### Step 6: Activate Domain (1 minute) ```bash nself whitelabel domain activate app.acme.com ``` ### Step 7: Test (2 minutes) ```bash # Check health nself whitelabel domain health app.acme.com # Test HTTPS curl -I https://app.acme.com # Check certificate nself ssl check app.acme.com ``` **Repeat for all tenants**: ```bash # TechCo nself whitelabel domain add app.techco.com --tenant techco nself whitelabel domain verify app.techco.com nself whitelabel domain ssl app.techco.com --auto-renew # StartupXYZ nself whitelabel domain add app.startupxyz.com --tenant startupxyz nself whitelabel domain verify app.startupxyz.com nself whitelabel domain ssl app.startupxyz.com --auto-renew ``` --- ## SSL Certificate Management ### Option 1: Let's Encrypt (Recommended for Production) **Automatic issuance and renewal** #### Initial Setup (5 minutes) ```bash # Install certbot sudo apt-get install certbot python3-certbot-nginx # Ubuntu/Debian # Or via snap sudo snap install --classic certbot ``` #### Generate Certificate ```bash nself ssl generate myapp.com --letsencrypt ``` **Behind the scenes**: ```bash certbot certonly --standalone \ -d myapp.com \ -d api.myapp.com \ -d auth.myapp.com \ -d admin.myapp.com \ --non-interactive \ --agree-tos \ --email admin@myapp.com ``` #### Auto-Renewal (1 minute) ```bash # Enable auto-renewal nself ssl enable-auto-renew myapp.com # Test renewal nself ssl renew myapp.com --dry-run # Check renewal status nself ssl status myapp.com ``` **Cron job created**: ```bash # Renew certificates daily at 2am 0 2 * * * /usr/bin/certbot renew --quiet && nself restart nginx ``` ### Option 2: Self-Signed (Development Only) ```bash nself ssl generate myapp.com --self-signed ``` **Trust certificate locally**: **macOS**: ```bash sudo security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain \ ssl/myapp.com/cert.pem ``` **Linux**: ```bash sudo cp ssl/myapp.com/cert.pem /usr/local/share/ca-certificates/myapp.crt sudo update-ca-certificates ``` **Windows**: ```powershell Import-Certificate -FilePath ssl\myapp.com\cert.pem -CertStoreLocation Cert:\LocalMachine\Root ``` ### Option 3: Custom Certificate (Enterprise) **If you have your own certificate**: ```bash nself ssl upload myapp.com \ --cert /path/to/certificate.crt \ --key /path/to/private.key \ --chain /path/to/ca-bundle.crt ``` **Certificate requirements**: - PEM format - Includes full certificate chain - Private key is unencrypted - Valid for at least 30 days --- ## DNS Configuration ### Common DNS Providers #### Cloudflare 1. **Log in** to [Cloudflare Dashboard](https://dash.cloudflare.com) 2. **Select** your domain 3. **DNS** tab 4. **Add record**: - Type: `A` - Name: `app` (or `@` for apex) - IPv4 address: `your-server-ip` - Proxy status: **DNS only** (gray cloud, not orange) - TTL: `Auto` or `300` 5. **Save** **Important**: Set proxy to **DNS only** (gray cloud), not proxied (orange cloud), for SSL to work correctly. #### GoDaddy 1. **Log in** to [GoDaddy](https://sso.godaddy.com) 2. **My Products** > **DNS** 3. **Add** record: - Type: `A` - Host: `app` (or `@`) - Points to: `your-server-ip` - TTL: `1 hour` 4. **Save** #### Namecheap 1. **Log in** to [Namecheap](https://www.namecheap.com/myaccount/login/) 2. **Domain List** > **Manage** 3. **Advanced DNS** 4. **Add New Record**: - Type: `A Record` - Host: `app` - Value: `your-server-ip` - TTL: `Automatic` 5. **Save** #### AWS Route 53 1. **Console** > **Route 53** > **Hosted zones** 2. **Select** domain 3. **Create record**: - Record name: `app` - Record type: `A` - Value: `your-server-ip` - TTL: `300` - Routing policy: `Simple` 4. **Create records** ### DNS Verification ```bash # Check A record dig app.myapp.com A # Check with specific DNS server dig @8.8.8.8 app.myapp.com A # Check propagation globally nself dns check app.myapp.com ``` **Expected output**: ``` app.myapp.com. 300 IN A your-server-ip ``` ### DNS Propagation Time **Typical**: - Cloudflare: 1-5 minutes - GoDaddy: 10-30 minutes - Namecheap: 30 minutes - 2 hours - AWS Route 53: 1-5 minutes **Check propagation**: - https://www.whatsmydns.net - https://dnschecker.org --- ## Wildcard Domains ### Use Case: Multi-tenant subdomains **Goal**: `{tenant}.myplatform.com` Examples: - `acme.myplatform.com` - `techco.myplatform.com` - `startup.myplatform.com` ### Step 1: DNS Wildcard Record (2 minutes) ``` Type Name Value TTL A * your-server-ip 300 ``` This matches: - `anything.myplatform.com` - `acme.myplatform.com` - `subdomain123.myplatform.com` ### Step 2: Wildcard SSL Certificate (5 minutes) **Let's Encrypt wildcard**: ```bash nself ssl generate myplatform.com --wildcard --letsencrypt ``` **DNS challenge required**: ``` Add this TXT record to myplatform.com: _acme-challenge "random-string-from-letsencrypt" ``` **Wait for DNS, then continue**: ```bash nself ssl verify myplatform.com ``` ### Step 3: Configure Nginx (3 minutes) Edit `.env`: ```bash WILDCARD_DOMAIN_ENABLED=true WILDCARD_DOMAIN=myplatform.com TENANT_ROUTING=subdomain # Route by subdomain ``` Rebuild: ```bash nself build nself restart nginx ``` ### Step 4: Test (2 minutes) ```bash # Test different subdomains curl -I https://acme.myplatform.com curl -I https://techco.myplatform.com curl -I https://any-subdomain.myplatform.com # All should work ``` --- ## Domain Verification ### Why Verify? - Proves domain ownership - Required for SSL issuance - Prevents unauthorized use ### Verification Methods #### Method 1: TXT Record (Recommended) ```bash nself whitelabel domain verify app.acme.com --method txt ``` **Add to DNS**: ``` Type Name Value TXT _nself-verify.app "verification-token-here" ``` **Verify**: ```bash nself whitelabel domain verify app.acme.com --check ``` #### Method 2: HTTP Challenge ```bash nself whitelabel domain verify app.acme.com --method http ``` **Host this file**: ``` URL: http://app.acme.com/.well-known/nself-verification.txt Content: verification-token-here ``` **Verify**: ```bash nself whitelabel domain verify app.acme.com --check ``` #### Method 3: DNS CNAME ```bash nself whitelabel domain verify app.acme.com --method cname ``` **Add to DNS**: ``` Type Name Value CNAME app yourplatform.com ``` ### Verification Status ```bash # Check status nself whitelabel domain status app.acme.com ``` **Output**: ``` Domain: app.acme.com Status: verified Method: txt Verified at: 2026-01-30 10:15:23 SSL: active Health: healthy ``` --- ## Production Deployment ### Pre-Deployment Checklist - [ ] DNS records configured - [ ] DNS propagated (verified with `dig` or `nslookup`) - [ ] Domain verified - [ ] SSL certificate issued - [ ] SSL auto-renewal enabled - [ ] Firewall allows ports 80 (HTTP) and 443 (HTTPS) - [ ] Server IP is correct in DNS - [ ] nginx configuration tested - [ ] Health checks passing ### Deploy Custom Domain **Step 1: Update production config** Edit `.env.prod`: ```bash BASE_DOMAIN=myapp.com ENVIRONMENT=production SSL_PROVIDER=letsencrypt SSL_AUTO_RENEW=true ``` **Step 2: Deploy** ```bash nself deploy prod ``` **Step 3: Generate SSL** ```bash # SSH to production server ssh user@your-server-ip cd /var/www/myapp # Generate certificate nself ssl generate myapp.com --letsencrypt --email admin@myapp.com # Enable auto-renewal nself ssl enable-auto-renew myapp.com ``` **Step 4: Verify** ```bash # Check certificate nself ssl check myapp.com # Test HTTPS curl -I https://api.myapp.com # Check all URLs nself urls ``` ### Multi-Domain Deployment **For multiple domains on one server**: ```bash # Domain 1 nself whitelabel domain add app1.com --tenant tenant1 nself whitelabel domain ssl app1.com --letsencrypt # Domain 2 nself whitelabel domain add app2.com --tenant tenant2 nself whitelabel domain ssl app2.com --letsencrypt # Domain 3 nself whitelabel domain add app3.com --tenant tenant3 nself whitelabel domain ssl app3.com --letsencrypt # Activate all nself whitelabel domain activate app1.com nself whitelabel domain activate app2.com nself whitelabel domain activate app3.com # Restart nself restart nginx ``` --- ## Troubleshooting ### DNS Not Resolving **Symptom**: `nslookup` returns `NXDOMAIN` or wrong IP **Fix**: ```bash # Check DNS records nself dns check app.myapp.com # Expected: # A record: app.myapp.com → your-server-ip # If wrong, update DNS at registrar # Wait for propagation (5 minutes - 48 hours) # Check globally curl "https://dns.google/resolve?name=app.myapp.com&type=A" ``` ### SSL Certificate Failed **Symptom**: `certbot` errors **Common causes**: 1. **Port 80 blocked**: ```bash # Check firewall sudo ufw status sudo ufw allow 80/tcp sudo ufw allow 443/tcp ``` 2. **Domain not pointing to server**: ```bash # Verify DNS first dig app.myapp.com A # Must return your server IP ``` 3. **Rate limit hit**: ``` Error: too many certificates already issued for myapp.com ``` **Solution**: Wait 1 week, or use staging: ```bash nself ssl generate myapp.com --letsencrypt --staging ``` ### Certificate Expired **Symptom**: Browser shows "Certificate expired" **Fix**: ```bash # Renew certificate nself ssl renew myapp.com # Check expiry nself ssl check myapp.com # Enable auto-renewal nself ssl enable-auto-renew myapp.com # Test renewal nself ssl renew myapp.com --dry-run ``` ### Domain Shows Wrong Site **Symptom**: `app.myapp.com` shows default nginx page **Fix**: ```bash # Check nginx configuration nself config show nginx | grep server_name # Should include: server_name app.myapp.com; # If missing, rebuild nself build nself restart nginx # Check site configuration cat nginx/sites/app.myapp.com.conf ``` ### SSL Mixed Content Warning **Symptom**: Page loads but shows "Not Secure" warning **Cause**: HTTP resources on HTTPS page **Fix**: 1. **Force HTTPS redirect**: ```bash # In .env FORCE_HTTPS=true nself build nself restart nginx ``` 2. **Update frontend**: ```javascript // Change all URLs to HTTPS // Bad: // Good: ``` ### Wildcard Certificate Not Covering Subdomain **Symptom**: `app.myapp.com` works, but `api.myapp.com` fails **Cause**: Wildcard only covers one level **Fix**: ```bash # Get certificate for both wildcard and apex nself ssl generate myapp.com \ --letsencrypt \ --domains "myapp.com,*.myapp.com" ``` --- ## Best Practices ### Security 1. **Always use HTTPS** - Redirect HTTP → HTTPS - Set `FORCE_HTTPS=true` 2. **Enable HSTS** ```bash # In .env HSTS_ENABLED=true HSTS_MAX_AGE=31536000 # 1 year ``` 3. **Monitor certificate expiry** ```bash # Check expiry nself ssl check myapp.com # Set up alerts (30 days before expiry) nself ssl alert myapp.com --days 30 --email admin@myapp.com ``` ### Performance 1. **Enable HTTP/2** ```bash # In .env HTTP2_ENABLED=true ``` 2. **Use CDN** (Cloudflare, CloudFront) ```bash # Configure CDN in .env CDN_ENABLED=true CDN_URL=https://cdn.myapp.com ``` 3. **Enable caching** ```bash # In .env NGINX_CACHE_ENABLED=true NGINX_CACHE_SIZE=100m ``` ### Reliability 1. **Monitor domain health** ```bash # Automated checks nself whitelabel domain health --all --interval 5m ``` 2. **Set up uptime monitoring** - Use: Pingdom, UptimeRobot, StatusCake - Monitor: `https://api.myapp.com/health` 3. **Keep SSL auto-renewal enabled** ```bash # Verify renewal is configured nself ssl status myapp.com | grep auto-renew ``` --- ## Quick Reference ### Common Commands ```bash # Add domain nself whitelabel domain add app.myapp.com # Verify domain nself whitelabel domain verify app.myapp.com # Generate SSL nself ssl generate myapp.com --letsencrypt # Check SSL status nself ssl check myapp.com # Renew SSL nself ssl renew myapp.com # Check DNS nself dns check app.myapp.com # View all domains nself whitelabel domain list # Domain health nself whitelabel domain health app.myapp.com ``` ### DNS Record Templates **Apex domain**: ``` A @ your-server-ip 300 A www your-server-ip 300 ``` **Subdomain**: ``` A app your-server-ip 300 ``` **Wildcard**: ``` A * your-server-ip 300 ``` **CNAME** (alternative): ``` CNAME app yourplatform.com 300 ``` **Verification**: ``` TXT _nself-verify.app "token" 300 ``` --- ## Resources - **[Let's Encrypt](https://letsencrypt.org)** - Free SSL certificates - **[DNS Checker](https://dnschecker.org)** - Check DNS propagation - **[SSL Labs](https://www.ssllabs.com/ssltest/)** - Test SSL configuration - **[nself SSL Command](../commands/SSL.md)** - Full SSL command reference --- ## Support - **Documentation**: https://docs.nself.org - **GitHub**: https://github.com/nself-org/cli - **Discord**: https://discord.gg/nself --- **Your custom domain is configured! Your app is live.**