First package? Follow the publish and install quickstart →
Overview
This guide tells you how to connect a custom domain, such as npm.acme.com, to a private registry. A custom domain is a hostname that you own. A private registry uses its custom domain as the URL for all publish and install requests.
You add two DNS records. A TXT record proves that you control the hostname. A CNAME record sends the traffic for the hostname to the registry edge. The edge then gets a TLS certificate for the hostname. The registry checks the TXT record, the CNAME record and the certificate. When the three checks pass, the domain becomes active and the registry serves packages on the hostname.
This guide is for administrators who configure a private registry in the npm, PyPI or Files format. You must have write access to the DNS zone of your domain. The shared registry at registry.privatenpm.com does not use a custom domain.
Before you start
- Plan. Each private registry counts against the registry limit of your plan. Starter permits 1 registry, Growth permits 3, and Scale and Platform have no limit. Custom domains have no separate limit. Refer to pricing.
- Role. You must have the Admin role. Only the Admin role has the
registry:domain:managepermission that the Domains page requires. - DNS access. You must be able to create TXT and CNAME records in the zone of the hostname.
- Tools. The examples use
dig,openssland the npm CLI.nslookupor the query tool of your DNS provider can replacedig. - Hostname. Select a hostname that has no other use, for example
npm.acme.comorpypi.acme.com.
A hostname must contain a dot and have 253 characters or fewer. Each label has 1 to 63 characters: lowercase letters, digits and hyphens. A label cannot start or end with a hyphen. The registry refuses IP addresses, underscores and wildcard names.
How it works
Verification has three steps. The Setup section of the domain page shows each step with the badge done, waiting or problem.
Step 1: Ownership (TXT record)
When you request a hostname, the registry makes a challenge. The challenge is a TXT record at _privatenpm-challenge. followed by your hostname. For npm.acme.com, the record name is _privatenpm-challenge.npm.acme.com. The value starts with privatenpm-verify= and continues with a random string.
Only a person who controls the DNS zone can publish that record. Thus a match proves ownership. The registry stores only a SHA-256 hash of the value, and compares the hash with each TXT answer. By default, a challenge is valid for 72 hours. The challenge page shows the exact expiry time.
After the registry finds the record, the domain status changes to verified. The registry does not query the TXT record again. If another organization verified the same hostname first, the registry refuses your request.
Step 2: DNS routing (CNAME)
The CNAME record points your hostname at the registry edge, edge.privatenpm.com. The edge is the TLS proxy that receives all registry traffic. npm, PyPI and Files hostnames use the same target.
The check of step 2 passes when the CNAME target is the edge. The check also passes when every address of the hostname is an address of the edge. This rule permits ALIAS, ANAME or CNAME flattening at a zone apex. The check reports other A or AAAA records. With such records, some clients do not reach the edge.
Step 3: Certificate
The edge uses on-demand TLS. When the first TLS connection for a new hostname arrives, the edge asks the registry if the edge can issue a certificate for that hostname. The registry permits only the platform hosts and custom domains that are verified and not revoked. Thus a hostname that points at the edge without a verified challenge never receives a certificate.
The edge then gets a certificate from a public certificate authority, usually Let's Encrypt. The certificate authority must reach the edge on ports 80 and 443. The certificate check of the registry opens a real TLS connection to port 443 of your hostname. That connection also starts the issue of the certificate. When the edge serves a publicly trusted certificate, the domain status changes to active. The edge renews the certificate automatically.
Check schedule
Automatic checks run 1, 1, 2, 5, 10, 15 and 30 minutes after the request, and then one time each hour. The schedule continues until the domain is active. If the challenge expires before the registry finds the TXT record, the status changes to failed and automatic checks stop.
For an active domain, a health check runs every 6 hours. The health check records problems in the check history. The health check does not stop traffic to an active domain. The Check now button runs all steps at once and starts the schedule again. You can use Check now one time each minute.
Domain status
| Status | Description | Serves traffic |
|---|---|---|
| pending | The registry waits for the TXT record. | No |
| verified | The registry found the TXT record. The CNAME record or the certificate is not ready. | No |
| active | The edge serves a trusted certificate. The registry uses this hostname. | Yes |
| failed | The challenge expired before the registry found the TXT record. | No |
| suspended | An administrator stopped the traffic. The organization keeps the hostname. | No |
| revoked | The organization released the hostname. Any organization can request the hostname again. | No |
DNS propagation and TTL
The checks use a recursive DNS resolver, not your authoritative name server. A new record usually becomes visible in a few minutes. If a record with the same name existed before, resolvers keep the old answer until its TTL (time to live) expires. If a resolver asked for the challenge name before you created the record, the resolver can keep the negative answer. The negative TTL comes from the SOA record of your zone.
For a fast change, set a TTL of 300 seconds on the two records. You can increase the TTL after the domain is active.
Request the hostname
You can request a hostname when you create a private registry, or later from the Domains page.
To create a new private registry with its hostname:
- Open Registry → Registries.
- Click New private registry.
- If the page shows Format, select the format.
- Type the hostname in Registry hostname.
- Click Create and verify domain. The challenge page opens and shows the DNS records.
To request a hostname for a private registry that exists:
- Open Registry → Domains.
- Expand Verify a registry hostname.
- Type the hostname in Hostname, for example
npm.acme.com. - Select the registry in Private registry.
- Click Verify hostname. The message "Domain registered; publish its DNS record." appears. Below the message, the challenge shows the DNS records.

The challenge has the title Verify domain ownership. The challenge shows the Name and Value of each record, with copy buttons.
Publish the DNS records
Make sure that the challenge page is open, and that you have access to the DNS provider of the zone.
- Create a TXT record with the Name and Value from the challenge page.
- Create a CNAME record from your hostname to
edge.privatenpm.com. - If your DNS provider has a proxy option, set the CNAME record to DNS only.
- Remove all other A, AAAA and CNAME records for the hostname.
- Save the zone.
For the zone acme.com, the records in zone file format are:
_privatenpm-challenge.npm.acme.com. 300 IN TXT "privatenpm-verify=<value from the challenge page>"
npm.acme.com. 300 IN CNAME edge.privatenpm.com.Many DNS providers add the zone name to the record name automatically. In such a provider, type _privatenpm-challenge.npm and npm as the names. If you type the full name, the provider can make _privatenpm-challenge.npm.acme.com.acme.com. The ownership check detects this error and tells you the name to use.
NOTE: DNS does not permit a CNAME record at a zone apex such as acme.com. At an apex, use the ALIAS, ANAME or CNAME flattening feature of your DNS provider, with the same target.
CAUTION: Do not proxy the CNAME record, for example with the orange cloud of Cloudflare. A proxy ends TLS before the edge, and the edge cannot get or serve the certificate.
Monitor the checks
- On the challenge page, click Open the domain. The domain page opens.
- Find the Setup section. Each step shows its badge and the result of the last check.
- Wait for the automatic checks, or click Check now. The message "Checked; see the results below." appears.
- Make sure that the three steps show done. The status in Details changes to active, and the certificate badge shows certificate active.
The Check history section shows the last 30 results. The history contains each manual check, and each automatic check that changed the result of a step. The Next automatic check field shows the time of the next check.
Configure the clients
Give the custom domain to recipients and CI only after the status is active. For npm, the scope route and the credential line must both use the custom domain:
@acme:registry=https://npm.acme.com/
//npm.acme.com/:_authToken=${ACME_NPM_TOKEN}npm sends a credential only to the host in the credential line. If the two hosts are different, npm sends no token, and the registry returns 401. For CI and pnpm configuration, refer to the .npmrc guide.
A private npm registry has no other URL. Publish requests to the registry fail until the domain is active.
PyPI and Files registries
PyPI and Files registries use the same TXT challenge, the same CNAME target and the same checks. On the shared PyPI or Files host, the URL contains the registry slug as the first path segment. On a verified custom domain, the URL has no slug. The registry serves the index or the files at the root of the hostname. Copy the exact URLs from the Setup page of the registry. Refer to the PyPI guide and the Files guide.
Change the hostname of a registry
A registry has one active hostname. The old hostname continues to serve traffic until the new hostname is active. When the new hostname becomes active, the registry revokes the old hostname in the same transaction. The old hostname does not redirect to the new hostname.
- Open Registry → Registries.
- Click Manage registry on the registry.
- In the Registry hostname card, expand Use a different hostname.
- Type the hostname in New hostname.
- Click Start hostname verification. The challenge page for the new hostname opens.
- Publish the DNS records of the new hostname.
- Monitor the checks of the new hostname.
A registry can have only one hostname that waits for verification. To start a different request, revoke the waiting request first.
CAUTION: Prepare the .npmrc changes for recipients and CI before the new hostname becomes active. When the new hostname becomes active, requests to the old hostname fail at once.
The change has these effects on clients:
- .npmrc files. Change the host in the scope route and in the credential line. The download keys and publisher keys stay valid.
- Lockfiles. A
package-lock.jsonfile contains aresolvedURL for each package, with the old hostname. By default, npm replaces the host of aresolvedURL only forregistry.npmjs.org. Setreplace-registry-host=alwaysin.npmrc. npm then uses the configured registry host for these URLs. - Installed packages. Packages in
node_modulesand in local caches continue to work. Only new downloads use the hostname. - Trusted publishers. On a custom domain, the OIDC audience contains the hostname. Update the audience in GitLab CI. Refer to npm trusted publishing.
Verify the result
Do these tests from a computer outside your company network, or from a clean CI runner.
- Query the TXT record:
dig +short TXT _privatenpm-challenge.npm.acme.com. The result is the value from the challenge page, in quotes. - Query the CNAME record:
dig +short CNAME npm.acme.com. The result isedge.privatenpm.com. - Examine the certificate with the command below. The subject contains
npm.acme.com, and the issuer is a public certificate authority.
openssl s_client -connect npm.acme.com:443 -servername npm.acme.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -enddateThen request package metadata. Use the .npmrc file from Configure the clients, and a download key that has access to a published package:
export ACME_NPM_TOKEN=pdk_...
npm view @acme/sdk versionThe expected result is the version number of the package. The TXT query can be empty after you delete the record. An empty TXT answer after verification is correct.
Troubleshoot
The Setup section shows the cause of each problem. The table gives the usual messages.
| Message or symptom | Cause | Correction |
|---|---|---|
| "No TXT record at … yet." | The record does not exist, or resolvers keep an old negative answer. | Make sure that the record exists. Wait for the negative TTL, then click Check now. |
| "… none matches the current challenge value." | The value is wrong or from an old challenge. | Replace the value with the value on the challenge page. |
| "The challenge was found at … instead of …" | The DNS provider added the zone name a second time. | Type the record name without the zone name. |
| Status failed; "The verification challenge expired …" | The registry did not find the TXT record before the expiry time. | Click Generate new TXT value, then update the TXT record. |
| "… has no DNS records yet." | The CNAME record does not exist. | Create the CNAME record to edge.privatenpm.com. |
| "… not to the registry edge." | The hostname points to a different server, or a proxy is on. | Point the CNAME at the edge. Set the record to DNS only. |
| "Remove the other A/AAAA records …" | Some addresses of the hostname are not edge addresses. | Remove the A and AAAA records for the hostname. |
| Certificate stays certificate pending; "TLS alert" | A CAA record excludes Let's Encrypt, or a firewall blocks ports 80 and 443. | Add letsencrypt.org to the CAA records. Open the ports to the edge. |
| "That hostname is already registered." | The hostname has an open request with a valid challenge, or a different organization verified the hostname. | Use the open request, or revoke the open request first. |
npm install returns E401 on the new host | The credential line uses a different host. | Change both .npmrc lines to the custom domain. |
If a check reports a failed lookup, the cause can be a temporary DNS error. Automatic checks continue. For other 401 errors, refer to the E401 checklist.
Security and maintenance
- TXT record. You can delete the TXT record after the domain status changes to verified. The registry does not query the record again.
- CNAME record. Keep the CNAME record while the domain is in use. If you remove the CNAME record, clients cannot reach the registry.
- Suspend. Use Suspend to stop the traffic for a short time. The organization keeps the hostname, and Resume starts the traffic again.
- Revoke. Use Revoke to release the hostname permanently. After you revoke a domain, remove its CNAME record from your zone.
- Health checks. The registry checks active domains every 6 hours and records problems in the check history. Examine the history after DNS or firewall changes.
- Audit and webhooks. The audit log records each request, change and revocation of a domain. On Growth and higher plans, the webhook events
registry_domain.created,registry_domain.updatedandregistry_domain.revokedare available. Refer to the webhooks guide.
WARNING: Do not give DNS write access to people who do not need it. A person who can change the CNAME record can send your registry traffic to a different server.
A revoked hostname is free for other organizations. To verify the hostname, a different organization must publish its own TXT challenge in your zone. Thus control of the DNS zone protects the hostname.