# Dynamic Subdomain Management & Automated SSL with Node.js and Certbot

## **Introduction**

Managing multiple subdomains dynamically while keeping the URL unchanged is a common requirement for SaaS platforms, multi-tenant applications, and personal website builders. Additionally, allowing users to add their own custom domains with **automatic SSL (HTTPS) certificates** ensures security and trust.

In this guide, we will explore:

* **Dynamically mapping subdomains to specific paths** using **Node.js**.
    
* **Handling user-provided custom domains** and automatically issuing **SSL certificates** using **Certbot**.
    
* **Using a reverse proxy** to serve the correct content without URL changes.
    

---

## **1\. Subdomain Mapping Without Redirection**

To keep the URL ([`nishi.hello.com`](http://nishi.hello.com)) unchanged while serving content from ([`hello.com/nishi`](http://hello.com/nishi)), we need a **reverse proxy**. Instead of using Nginx, we will handle everything in **Node.js**.

### **1.1 Install Dependencies**

```bash
npm install express http-proxy-middleware
```

### **1.2 Implement Subdomain Handling in Express**

```javascript
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// Middleware to handle subdomains
app.use((req, res, next) => {
    const host = req.hostname; // e.g., "nishi.hello.com"
    const mainDomain = "hello.com";

    if (host.endsWith(`.${mainDomain}`)) {
        const subdomain = host.split('.')[0]; // Extract the subdomain

        if (subdomain !== "www") {
            return createProxyMiddleware({
                target: `https://${mainDomain}`,
                changeOrigin: true,
                pathRewrite: { '^/$': `/${subdomain}` },
            })(req, res, next);
        }
    }

    next(); // Continue to default routes if no subdomain
});

// Default main domain route
app.get('/', (req, res) => {
    res.send('Welcome to hello.com!');
});

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});
```

### **1.3 Configure Wildcard DNS**

Add an `A` or `CNAME` record for `*.`[`hello.com`](http://hello.com) pointing to your server.

---

## **2\. Adding Custom Domains with SSL (HTTPS)**

Now, let’s allow users to connect their own domains ([`userdomain.com`](http://userdomain.com)) and issue **SSL certificates** automatically.

### **2.1 Install Certbot**

```javascript
sudo apt update
sudo apt install certbot python3-certbot-nginx -y
```

### **2.2 Allow Node.js to Bind Ports 80 & 443**

```javascript
sudo setcap 'cap_net_bind_service=+ep' $(which node)
```

### **2.3 Implement Custom Domain Handling in Express**

```javascript
const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();
app.use(express.json());

const domains = {}; // Store user domains and target mappings

// Route to allow users to add their own domains
app.post('/add-domain', (req, res) => {
    const { domain, target } = req.body;

    if (!domain || !target) {
        return res.status(400).json({ error: "Domain and target are required." });
    }

    // Store domain mapping
    domains[domain] = target;

    // Run Certbot to generate SSL certificate
    exec(`sudo certbot certonly --nginx -d ${domain} --non-interactive --agree-tos --email admin@${domain}`, (err, stdout, stderr) => {
        if (err) {
            console.error(`Error generating SSL: ${stderr}`);
            return res.status(500).json({ error: "SSL setup failed." });
        }
        console.log(`SSL issued for ${domain}`);
        res.json({ message: `Domain ${domain} added with SSL.` });
    });
});

// Middleware to proxy custom domains
app.use((req, res, next) => {
    const host = req.hostname; // Get requested domain

    if (domains[host]) {
        return createProxyMiddleware({
            target: domains[host],
            changeOrigin: true
        })(req, res, next);
    }

    next();
});

// Start HTTPS Server
const https = require('https');
const options = {
    key: fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem'),
    cert: fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem'),
};

https.createServer(options, app).listen(443, () => {
    console.log("Secure server running on port 443");
});

// Start HTTP server for Certbot challenges
app.listen(80, () => {
    console.log("Server listening on port 80");
});
```

---

## **3\. Testing the Setup**

### **3.1 Adding a Custom Domain**

Users can add their domain via API:

```javascript
curl -X POST http://yourserver.com/add-domain \
     -H "Content-Type: application/json" \
     -d '{"domain": "userdomain.com", "target": "https://example.com/user"}'
```

✔️ [`userdomain.com`](http://userdomain.com) will now load [`example.com/user`  
✔️](http://example.com/user￼✔️) HTTPS is automatically enabled

### **3.2 Verify DNS**

Before adding a domain, users must point their domain to your server:

```javascript
nslookup userdomain.com
```

Ensure it resolves to your server’s IP.

---

## **4\. Automating SSL Renewal**

Let’s Encrypt certificates expire every **90 days**. Set up auto-renewal:

```javascript
sudo crontab -e
```

Add:

```javascript
0 0 * * * certbot renew --quiet
```

This **renews SSL** daily at midnight.

---

## **5\. Additional Enhancements**

✅ **Database Storage**: Store user domains in a database instead of an object.  
✅ **Authentication**: Require API keys or user authentication.  
✅ **Load Balancing**: Use Cloudflare or AWS Load Balancer for better scaling.

---

## **Conclusion**

🎯 Now, we have a **fully dynamic subdomain and custom domain management system** in Node.js!  
🚀 Users can **add their domains**, get **SSL automatically**, and have their domain **proxy to the correct content**.  
🔐 Certbot handles **HTTPS security** without manual intervention.

This setup is **perfect for multi-tenant apps, SaaS platforms, and website builders**. Want to improve it further? Let me know in the comments! 🚀
