# Let's Encrypt

- Tool: Bubble
- Difficulty: Beginner
- Time required: 30–60 minutes
- Last updated: July 2026

## TL;DR

Let's Encrypt SSL on Bubble apps is fully automatic — add a custom domain in Settings → Domain/email and Bubble provisions a free TLS certificate within minutes. The actionable part is setting up Let's Encrypt via Certbot on external companion services (APIs, webhook receivers) that Bubble calls through the API Connector, since Bubble requires HTTPS for all outbound API calls.

## SSL on Bubble: What's Automatic and What Isn't

When people search 'Bubble Let's Encrypt', they usually have one of two questions: 'Does my Bubble app have SSL?' (yes, automatically) or 'Why is Bubble's API Connector rejecting my external service?' (missing HTTPS on the external side). Understanding which problem you're solving saves hours of confusion.

Bubble's own hosting infrastructure uses Let's Encrypt certificates under the hood. When you add a custom domain via Settings → Domain/email, Bubble completes the ACME HTTP-01 challenge on its own servers and issues a certificate within 5–15 minutes of DNS propagation. Bubble also handles 90-day automatic renewal. You never see a certificate file, and you never run Certbot inside Bubble.

The scenario that requires your attention is when your Bubble app calls an external service via the API Connector — a Node.js microservice on a VPS, a webhook receiver, a Dockerized API. Bubble's API Connector only makes HTTPS calls. If your external service serves plain HTTP, or if it uses a self-signed certificate, Bubble will refuse the connection. This is where Certbot and Let's Encrypt come in: you install Certbot on your VPS, obtain a certificate for your service's domain, and Nginx or Apache handles the HTTPS termination. After that, Bubble's API Connector can reach your service without any issues.

## Before you start

- A Bubble app on any plan (custom domain connection works on all plans including Free)
- A registered custom domain with access to your DNS provider's dashboard (Cloudflare, Namecheap, GoDaddy, etc.)
- For external services only: a Linux VPS (Ubuntu 20.04+ or Debian 11+) with Nginx installed and a domain pointed at its IP
- For external services only: SSH access to the VPS and sudo privileges

## Step-by-step guide

### 1. Add Your Custom Domain to Bubble

Open your Bubble app in the editor. In the top navigation, click Settings (the gear icon), then click the Domain/email tab in the left panel. Under 'Custom domain', type your domain — for example, myapp.com or app.mycompany.com. Click 'Add domain'.

Bubble displays a CNAME record instruction: point your domain at Bubble's infrastructure by creating a CNAME record in your DNS provider. The exact value looks something like `hosting.bubbleapps.io`. Log in to your DNS provider's dashboard (Cloudflare, Namecheap, Google Domains, or similar), find your domain's DNS settings, and add the CNAME record Bubble specifies.

For a root domain (myapp.com without 'www'), your DNS provider may require an ALIAS or ANAME record instead of a CNAME — Cloudflare handles this automatically when you proxy the record. For a subdomain like app.myapp.com, a standard CNAME works everywhere.

DNS changes can take anywhere from a few minutes to 48 hours to propagate globally. You can check current propagation status by visiting a DNS lookup tool and searching for your domain's CNAME record. Do not proceed to the next step until the CNAME resolves to Bubble's server.

**Expected result:** Bubble's Domain/email settings show your domain with a 'DNS verified' status indicator. The CNAME record is visible when you do a DNS lookup for your domain.

### 2. Wait for Bubble to Provision the Let's Encrypt Certificate

Once Bubble detects that your DNS CNAME is pointing to its servers, it automatically initiates the Let's Encrypt ACME HTTP-01 challenge. Bubble's infrastructure responds to Let's Encrypt's validation request on your behalf — you don't need to do anything manually.

Certificate issuance typically takes 5 to 15 minutes after DNS propagation completes. You can monitor progress in Bubble's Settings → Domain/email page, where the status will update from 'Pending' to 'Active'. If it doesn't update after 30 minutes, the most common cause is that the DNS record hasn't fully propagated yet.

While waiting, do not repeatedly click 'Add domain' or attempt to re-add the domain — this can confuse Bubble's provisioning queue. Simply wait and refresh the settings page every few minutes.

Once the certificate is active, visit your custom domain in a browser. You should see the padlock icon in the address bar indicating a valid TLS connection. Bubble also configures an automatic HTTP → HTTPS redirect so visitors who type http:// are forwarded to the secure version.

**Expected result:** Your Bubble app loads on your custom domain over HTTPS with a valid Let's Encrypt certificate (padlock icon visible). The Domain/email settings page shows the domain status as 'Active'.

### 3. Deploy Your Companion Service and Point a Subdomain at It

If you have a Bubble app that calls an external microservice — a Python API, Node.js REST server, or any custom backend — that service needs to be reachable over HTTPS before Bubble's API Connector can call it. The Let's Encrypt certificate for this service is separate from Bubble's own certificate.

First, deploy your service to a cloud host with a public IP address. Good options include DigitalOcean Droplets, Railway, Render, Fly.io, and Hetzner Cloud. Avoid running services on localhost — Bubble's servers cannot reach your local machine.

Once deployed, choose a subdomain for the service — for example, `api.myapp.com`. Go to your DNS provider and create an A record pointing this subdomain to your VPS's public IP address. For example:

- Type: A
- Name: api
- Value: 203.0.113.42 (your VPS IP)
- TTL: 300

Confirm the A record resolves correctly using a DNS lookup tool before proceeding. Certbot needs to reach your server on port 80 during the certificate challenge, so make sure your VPS firewall allows inbound traffic on ports 80 (HTTP) and 443 (HTTPS).

```
# DNS record to add at your provider
Type:  A
Name:  api          (this creates api.myapp.com)
Value: 203.0.113.42  (replace with your VPS public IP)
TTL:   300
```

**Expected result:** Running `dig A api.myapp.com` returns your VPS IP address. The A record is live and the subdomain resolves.

### 4. Install Nginx and Certbot on Your VPS

SSH into your VPS and install Nginx (the web server that will handle HTTPS termination) and Certbot (the Let's Encrypt client). The commands below work on Ubuntu 20.04 and 22.04. For Debian 11+, the package names are the same.

After installation, start Nginx and enable it to start automatically on server restart. Verify Nginx is running by visiting your server's IP address in a browser — you should see the default Nginx welcome page.

Certbot's Nginx plugin (`python3-certbot-nginx`) does two things automatically: it obtains the Let's Encrypt certificate and modifies your Nginx configuration to serve HTTPS on port 443. This is the recommended approach because it handles the certificate installation and web server configuration in a single command.

Before running Certbot, make sure your firewall allows traffic on port 80 (required for the ACME HTTP-01 challenge) and port 443 (for HTTPS). On Ubuntu with UFW enabled, run `sudo ufw allow 'Nginx Full'` to open both ports.

```
# 1. Update package index
sudo apt update

# 2. Install Nginx
sudo apt install nginx -y

# 3. Start Nginx and enable auto-start
sudo systemctl start nginx
sudo systemctl enable nginx

# 4. Install Certbot and its Nginx plugin
sudo apt install certbot python3-certbot-nginx -y

# 5. Open firewall for HTTP and HTTPS (if using UFW)
sudo ufw allow 'Nginx Full'
sudo ufw status
```

**Expected result:** Nginx is running (`sudo systemctl status nginx` shows 'active (running)'). Certbot and the Nginx plugin are installed (`certbot --version` returns a version number). Visiting your VPS IP in a browser shows the Nginx default welcome page.

### 5. Obtain the Let's Encrypt Certificate with Certbot

Run the Certbot command with the `--nginx` flag and your subdomain. Certbot will automatically communicate with Let's Encrypt, complete the ACME HTTP-01 challenge (Let's Encrypt makes a request to `http://api.myapp.com/.well-known/acme-challenge/...` and Certbot responds), obtain the certificate, and update your Nginx configuration to enable HTTPS.

During the process, Certbot asks for your email address (for renewal notices) and whether to redirect HTTP traffic to HTTPS — choose option 2 (Redirect) to enforce HTTPS.

After Certbot finishes, it sets up an automatic renewal timer. Let's Encrypt certificates expire every 90 days, but Certbot installs a systemd timer (`certbot.timer`) that renews certificates automatically when they are within 30 days of expiry. You can verify the timer is active with `sudo systemctl status certbot.timer`.

Visit `https://api.myapp.com` in your browser to verify the certificate is working. The padlock icon should appear, and you should see your service's response (or the Nginx default page if your application isn't running yet).

```
# Obtain certificate and auto-configure Nginx
# Replace api.myapp.com with your actual subdomain
sudo certbot --nginx -d api.myapp.com

# Certbot will ask:
# 1. Your email address (for renewal warnings)
# 2. Agree to terms of service (A)
# 3. Share email with EFF (optional)
# 4. Redirect HTTP to HTTPS (choose 2: Redirect)

# Verify automatic renewal is configured
sudo systemctl status certbot.timer

# Test a manual renewal dry-run
sudo certbot renew --dry-run
```

**Expected result:** Certbot reports 'Congratulations! Your certificate and chain have been saved'. Visiting `https://api.myapp.com` shows a valid padlock certificate in the browser. The `certbot.timer` service is 'active (waiting)'.

### 6. Connect the HTTPS Endpoint to Bubble's API Connector

Now that your companion service is reachable over HTTPS, add it to Bubble's API Connector. In your Bubble editor, click Plugins in the left sidebar, then click 'Add plugins', search for 'API Connector', and install it (it's the official plugin by Bubble). If you already have it installed, click on it.

Click 'Add another API' and give it a name matching your service (e.g., 'My Python API'). In the root URL field, enter your HTTPS endpoint: `https://api.myapp.com`. If your service requires an authentication token, add it as a shared header — click 'Add a shared header', enter `Authorization` as the key, and the value `Bearer YOUR_TOKEN`. Tick the 'Private' checkbox on that header so the token stays server-side and is never exposed to the browser.

Click 'Add a call' to define a specific endpoint. Set the method (GET, POST) and the path (e.g., `/health` or `/process`). Click 'Initialize call' — this sends a real request to your service and lets Bubble detect the response structure. You need a live response for initialization to succeed; if your service isn't returning data yet, add a simple `/health` endpoint that returns `{"status": "ok"}`.

Once initialized, the call is ready to use in Bubble workflows: go to Workflow editor → Add action → Plugins → [Your API name] → [Your call name].

```
// Sample Bubble API Connector configuration (conceptual)
{
  "api_name": "My Python API",
  "root_url": "https://api.myapp.com",
  "shared_headers": [
    {
      "key": "Authorization",
      "value": "Bearer YOUR_SECRET_TOKEN",
      "private": true
    }
  ],
  "calls": [
    {
      "name": "Health Check",
      "method": "GET",
      "path": "/health",
      "use_as": "Data"
    },
    {
      "name": "Process Request",
      "method": "POST",
      "path": "/process",
      "body": {
        "input": "<dynamic>"
      },
      "use_as": "Action"
    }
  ]
}
```

**Expected result:** The API Connector shows 'Initialized' status for your call. Bubble's 'Initialize call' response matches the JSON structure your service returns. The call is available in the Workflow editor under Plugins actions.

## Best practices

- Never upload a manually obtained Let's Encrypt certificate to Bubble's hosting — Bubble manages its own certificate lifecycle completely and doesn't provide a certificate upload interface. Attempting manual certificate management on the Bubble side wastes time and isn't needed.
- For external services that Bubble calls, always use a dedicated subdomain (api.myapp.com) rather than a bare IP address. Let's Encrypt doesn't issue certificates for IP addresses, and Bubble's API Connector with a stable subdomain is easier to update when you change hosting providers.
- Enable Certbot's automatic renewal and verify it immediately after setup with `sudo certbot renew --dry-run`. A certificate that expires because renewal silently failed will take your Bubble API Connector integration offline until the cert is renewed — check the timer with `sudo systemctl status certbot.timer`.
- Do not expose Docker Engine API or internal service ports publicly to get around HTTPS requirements. Instead, run Nginx as a reverse proxy in front of your service and terminate TLS there. This is cleaner, more secure, and the pattern Certbot is designed for.
- Add Bubble's custom domain connection and any external HTTPS endpoints to your project documentation. When teammates join or the project is handed off, they need to know which services have Let's Encrypt certificates and where those certificates are managed (Bubble-automatic vs. VPS Certbot).
- For wildcard certificates (*.myapp.com) that cover all subdomains, use the DNS-01 ACME challenge via `certbot --dns-cloudflare -d '*.myapp.com'`. HTTP-01 only works for specific subdomains. Wildcard certs are useful if your Bubble app dynamically creates customer subdomains or if you have many companion services under the same domain.
- Monitor Let's Encrypt rate limits if you're frequently re-issuing certificates during development. The limit is 50 certificates per registered domain per week — hitting it locks out all certificate issuance for that domain for the rest of the week. Use `--dry-run` for testing and `--staging` flag for development to avoid consuming production certificate quota.

## Use cases

### Add a Custom Domain to Your Bubble App

Point your branded domain (myapp.com or app.mycompany.com) at your Bubble app so visitors see your domain in the browser bar with a padlock — not the default bubbleapps.io URL. Bubble provisions the Let's Encrypt certificate automatically after you add the domain and update your DNS records.

Prompt example:

```
I want to connect my custom domain myapp.com to my Bubble app. What DNS records do I add and how long does SSL take to activate?
```

### Secure a Companion Microservice for Bubble's API Connector

Your Bubble app needs to call a custom Python API or Node.js service running on a VPS. Bubble's API Connector requires HTTPS, so you deploy the service behind Nginx and use Certbot to obtain a free Let's Encrypt certificate, making the endpoint reachable from Bubble.

Prompt example:

```
I have a Flask API at api.myapp.com on a DigitalOcean droplet. How do I get a Let's Encrypt certificate so Bubble's API Connector can call it over HTTPS?
```

### Enable HTTPS on a Bubble Backend Workflow Webhook Receiver

A third-party service (payment processor, CRM, shipping provider) needs to send webhook events to your Bubble app. Bubble's Backend Workflow endpoint at bubbleapps.io/api/1.1/wf/... is already HTTPS — but if you built a standalone webhook relay service on a VPS, it needs its own TLS certificate before it can reliably forward events to Bubble.

Prompt example:

```
I have a Node.js webhook relay server that forwards Stripe events to Bubble. How do I secure it with Let's Encrypt so Stripe accepts my HTTPS endpoint?
```

## Troubleshooting

### Bubble's Domain/email page shows 'Pending' for 30+ minutes after adding the custom domain

Cause: The DNS CNAME record hasn't propagated globally yet, or the CNAME value was entered incorrectly in your DNS provider. Bubble can't complete the Let's Encrypt ACME challenge until it can verify domain ownership via HTTP.

Solution: Go to your DNS provider and confirm the CNAME record exists and points to Bubble's specified value (typically `hosting.bubbleapps.io` or similar). Use a DNS propagation checker tool to verify the record is visible from multiple geographic locations. DNS changes can take up to 48 hours for full global propagation, though 5–30 minutes is typical with most providers. Do not re-add the domain in Bubble during this wait.

### Bubble's API Connector returns 'There was an issue setting up your call' or a connection error when calling an external service

Cause: The external service is either serving plain HTTP (not HTTPS), has a self-signed certificate that isn't trusted by public CAs, or Certbot didn't complete successfully so the certificate isn't installed correctly.

Solution: First, test the HTTPS endpoint directly in your browser — if you see a certificate warning or 'Not Secure', the certificate isn't valid. SSH into your VPS and run `sudo certbot certificates` to see whether the certificate was issued. If it wasn't, re-run `sudo certbot --nginx -d api.yourdomain.com`. Also verify your application is actually listening on port 443 (or that Nginx is proxying to it correctly) with `sudo netstat -tlnp | grep 443`.

```
sudo certbot --nginx -d api.yourdomain.com
sudo nginx -t && sudo systemctl reload nginx
```

### Let's Encrypt certificate renewal fails with 'Challenge failed for domain'

Cause: The HTTP-01 ACME challenge requires Let's Encrypt's servers to reach your VPS on port 80. If your firewall was tightened after initial setup and port 80 is now blocked, renewal will fail even if HTTPS (port 443) is open.

Solution: Temporarily open port 80 on both your VPS firewall (UFW) and any cloud-level network firewall, then trigger a manual renewal. Certbot uses port 80 only during the challenge; after the certificate is renewed, you can close port 80 again if your Nginx config redirects HTTP to HTTPS (the redirect itself handles the challenge before the port closes).

```
sudo ufw allow 80
sudo certbot renew --dry-run
sudo certbot renew
```

### The custom domain shows a browser certificate warning ('Your connection is not private') even after Bubble says the domain is active

Cause: Browser cache or a CDN layer is still serving the old (pre-SSL) response, or you're visiting via `http://` rather than `https://`. Alternatively, DNS is pointing to the wrong server and the certificate there doesn't match your domain.

Solution: Clear your browser cache and visit the domain with `https://` explicitly. If using Cloudflare in 'Proxied' mode, check that Cloudflare's SSL/TLS setting is 'Full (strict)' and not 'Flexible'. In Flexible mode, Cloudflare connects to Bubble over HTTP — the browser padlock still appears but the Bubble-to-Cloudflare leg is unencrypted. Switch to Full (strict) so the full chain is TLS-encrypted.

## Frequently asked questions

### Does my Bubble app already have HTTPS/SSL?

Yes. All Bubble apps on the default `*.bubbleapps.io` domain and all custom domains added through Bubble's Settings → Domain/email are automatically secured with Let's Encrypt certificates. Bubble handles provisioning, installation, and renewal entirely — you never need to touch a certificate file for your Bubble app itself.

### Why does Bubble's API Connector reject my external service even though it's running?

Bubble's API Connector only connects to HTTPS endpoints. If your service is running on plain HTTP (http://), or if the certificate is self-signed or expired, Bubble will refuse the connection. Use Certbot to obtain a free Let's Encrypt certificate and configure Nginx as a reverse proxy. After that, update the API Connector to use `https://` and re-initialize the call.

### Can I use a paid SSL certificate from another CA instead of Let's Encrypt?

For external companion services, yes — Bubble's API Connector trusts any certificate issued by a publicly trusted certificate authority (DigiCert, Comodo, Sectigo, etc.), not just Let's Encrypt. However, paid certificates typically cost $50–$300/year for no additional security benefit compared to Let's Encrypt. Let's Encrypt is trusted by all major browsers and operating systems and is the recommended choice for its zero cost and automated renewal.

### How long does Bubble's certificate provisioning take after I add a custom domain?

Typically 5 to 15 minutes after your DNS CNAME record fully propagates to Bubble's servers. DNS propagation itself can range from a few minutes (Cloudflare) to up to 48 hours (some legacy DNS providers). Check propagation at dnschecker.org before waiting for Bubble — the certificate won't issue until Bubble can verify domain ownership via the DNS record.

### Do Let's Encrypt certificates for external services affect my Bubble plan or WU budget?

No. Let's Encrypt certificates are managed entirely on your VPS by Certbot — there is no connection to Bubble's plan tier or WU budget. WU (Workload Units) are consumed by Bubble workflows when they make API Connector calls to your HTTPS service, but the certificate itself has no WU cost.

### What happens to my Bubble app's SSL when I switch to a new custom domain?

When you remove an old custom domain and add a new one in Settings → Domain/email, Bubble automatically provisions a new Let's Encrypt certificate for the new domain. The old certificate is automatically retired. There's no manual step — just update the DNS CNAME for the new domain and wait for Bubble to verify and provision the certificate.

---

Source: https://www.rapidevelopers.com/bubble-integrations/let-s-encrypt
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/let-s-encrypt
