# Vercel custom domain SSL: DNS records, CAA and fixes

> How Vercel issues Let's Encrypt certificates for custom domains: apex A vs www CNAME records, CAA values, wildcards, Enterprise uploads and errors.

Source: https://ssl.sh/guides/vercel-custom-domain-ssl/
Last updated: 2026-11-10

Vercel gets a free Let's Encrypt certificate for every custom domain you add to a project. Issuance starts once your DNS records point at Vercel, and Vercel renews the certificate on its own 14 to 30 days before it expires. Only Enterprise teams can upload their own certificate.

Normal hostnames are validated over HTTP-01. Wildcards need DNS-01, so Vercel has to control the `_acme-challenge` name. Steps and screen names follow Vercel's documentation as of November 2026, with `example.com` as the domain.

## At a glance

| Question | Vercel |
|---|---|
| CA for automatic certificates | Let's Encrypt |
| Validation | HTTP-01 for normal hostnames, DNS-01 for wildcards |
| Apex record (`example.com`) | `A` record with the IP shown in **Settings > Domains** |
| `www` and other subdomains | `CNAME` to the project's target, e.g. `d1d4fc829fe7bc7c.vercel-dns-017.com` |
| CAA must allow | `letsencrypt.org` |
| Wildcard certificates | Yes, with Vercel nameservers or a delegated `_acme-challenge` |
| Upload your own certificate | Enterprise plan only |
| Renewal | Automatic, 14 to 30 days before expiry |

## Step 1: Add the domain to the project

Open the project, go to **Settings > Domains** and click **Add Existing**, or use the CLI:

```bash
vercel domains add example.com my-project
```

When you add an apex domain, Vercel offers to add `www` too. Add both and redirect one to the other. Vercel recommends making `www` the primary domain, because a CNAME gives it more routing flexibility than a fixed A record.

## Step 2: Create the DNS records

Copy the record values from the dashboard, not from another project or an old tutorial. Vercel assigns anycast addresses per project. Many projects show `76.76.21.21` and newer ones can show an address such as `216.198.79.1`. A stale value leaves the domain in **DNS Change Recommended**, **DNS Change Required** or **Invalid Configuration**.

| Type | Name | Value |
|---|---|---|
| `A` | `@` | the A value shown for `example.com` |
| `CNAME` | `www` | the CNAME target shown for `www.example.com` |

Remove every other `A` and `AAAA` record for the apex first. Vercel does not support IPv6 for domains on external DNS yet, so an `AAAA` record can only send visitors somewhere else. The apex gets an `A` record rather than a CNAME because RFC 1034 does not allow a CNAME next to the `NS` and `MX` records that live at the zone apex. When you copy the CNAME value, keep the trailing dot if your DNS provider's form expects an absolute name.

If another Vercel account already uses the domain, Vercel asks you to verify access with a `TXT` record at `_vercel.example.com`. You can also move the whole zone to Vercel nameservers instead, but copy your `MX` and verification `TXT` records first.

## Step 3: Check CAA records

Existing CAA records must include Let's Encrypt. A domain with no CAA records can get certificates from any CA.

```dns
example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
example.com.  3600  IN  CAA  0 issuewild "letsencrypt.org"
```

The `issuewild` line matters only if you publish an `issuewild` policy at all, because for wildcard names it takes precedence over `issue`. CAA lookups follow CNAMEs. For `www.example.com` aliased to a Vercel target, Vercel's own CAA policy on that target applies, and you cannot add issuers to it. If you need another CA on that hostname, Vercel's advice is to replace the CNAME with the recommended A record and publish CAA at that name. Check the result with the [CAA checker](https://ssl.sh/tools/caa-checker/) or from a terminal:

```bash
dig CAA example.com +noall +answer
dig CAA www.example.com +noall +answer
```

## Wildcards and your own certificate

A wildcard such as `*.example.com` can only be validated with DNS-01. The simplest route is Vercel nameservers. If you must keep your DNS provider, delegate only the challenge name: on the team's **Domains** page select the domain and click **Enable Vercel DNS** (leave the registrar's nameservers unchanged), then add these records at your provider:

| Type | Name | Value |
|---|---|---|
| `NS` | `_acme-challenge` | `ns1.vercel-dns.com.` |
| `NS` | `_acme-challenge` | `ns2.vercel-dns.com.` |
| `CNAME` | `*` | `cname.vercel-dns-0.com.` |

Leave the NS records in place, because Vercel needs them for every renewal. Delegating the challenge name also stops other providers from validating the same name.

Uploading a certificate is an Enterprise feature. Use the dashboard, the REST API or the CLI:

```bash
vercel certs add --crt certificate.pem --key private-key.pem --ca ca.pem
```

Uploaded certificates must contain a Subject Alternative Name. Wildcards are accepted, and a certificate for an explicit subdomain wins over a wildcard. Vercel cannot renew them: when an uploaded certificate is within 5 days of expiry, Vercel falls back to its own automatic certificate.

## Verify

The issuer should be a Let's Encrypt intermediate unless you uploaded your own certificate. Check the deployment from outside with the [SSL checker](https://ssl.sh/tools/ssl-checker/), or from a terminal:

```bash
curl ssl.sh/example.com
```

Confirm the issuer, names and dates with OpenSSL:

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject -ext subjectAltName -dates
```

To confirm the HTTP-01 path reaches Vercel, request a made-up token; a `404` with `server: Vercel` is the expected answer:

```bash
curl -sS -D - -o /dev/null http://example.com/.well-known/acme-challenge/test-token
```

## Automatic renewal

Vercel attempts renewal 14 to 30 days before expiry, so a certificate that has not renewed at the 30-day mark is not necessarily a problem. If renewal fails, team owners get a **Configuration - Certificate renewal failed** notification. The usual causes are DNS records that no longer point at Vercel, a proxy blocking the challenge path, or a CAA change.

Let's Encrypt stopped sending expiry emails on June 4, 2025, and its default certificates shrink from 90 to 64 days on February 10, 2027 and to 45 days on February 16, 2028. Vercel handles the shorter lifetimes, but nobody warns you if DNS drifts away, so add the domain to [ssl.sh monitoring](https://ssl.sh/monitor/) (in development).

## Troubleshooting

- **Invalid Configuration**: the records at your DNS provider do not match the dashboard, a CAA record blocks Let's Encrypt, or a wildcard domain has no DNS challenge access. Compare `dig A example.com +short` and `dig CNAME www.example.com +short` with **Settings > Domains**.
- **DNS Change Recommended** or **DNS Change Required**: you are using a Vercel IP from another project or an old guide. Switch to the value shown for this project.
- **Certificate never issues behind a proxy**: route `/.well-known/acme-challenge/*` to Vercel on port 80 without caching, authentication, rewrites or redirects, or set Cloudflare records to **DNS only**. The `/.well-known` path is reserved on Vercel and cannot be redirected or rewritten.
- **CAA errors**: see [CAA record prevents issuance](https://ssl.sh/errors/caa-record-prevents-issuance/). Vercel also suggests testing with Let's Debug and DNSViz.
- **Stale `_acme-challenge` records** left by a previous provider can redirect DNS-01 validation away from Vercel; check them with `dig -t TXT _acme-challenge.example.com`.
- **`ERR_SSL_PROTOCOL_ERROR`**: compare with the deployment's `vercel.app` URL and try another network. If only one network fails, TLS inspection or filtering is likely; see [ERR_SSL_PROTOCOL_ERROR](https://ssl.sh/errors/err-ssl-protocol-error/).
- **`ERR_TOO_MANY_REDIRECTS` behind Cloudflare**: Cloudflare's Flexible SSL mode is a common cause.
