Self-Host a NIP-05 Nostr Identity on Your Domain
NIP-05 lets you tie your Nostr public key to a human-readable identifier like you@yourdomain.com. You self-host it by placing a single JSON file at https://yourdomain.com/.well-known/nostr.json and returning the correct CORS header. No third-party service needed, no ongoing fees — your domain, your identity.
This guide covers what NIP-05 actually does (and what it does not do), the three practical implementation paths — static file, WordPress dynamic route, and a dedicated lightweight service — plus the CORS requirements that trip up most first-time setups. Credits to the Nostr protocol contributors and the nostr-protocol/nips community whose open specification makes this possible.
What NIP-05 is (and what it is not)
Nostr’s core identity is a cryptographic key pair. Your public key looks like npub1abc…xyz in Bech32 display format, or a 64-character hex string at the protocol level. Memorising or sharing a 64-character hex string is impractical. NIP-05 solves the UX problem: it maps your public key to an email-shaped identifier (alice@example.com) that clients display and let people search by.
When a Nostr client encounters a nip05 field in a profile, it fetches:
GET https://example.com/.well-known/nostr.json?name=alice
It checks that the returned JSON contains Alice’s hex public key. If it matches, the client shows a verified badge or checkmark next to the identifier. That is the entire mechanism — elegant and minimal.
What NIP-05 is NOT
- Not a proof of domain ownership via cryptography. Anyone who can write to your webroot can claim your domain identity. NIP-05 trust is domain trust, not cryptographic proof.
- Not a replacement for your private key. Lose your private key and the NIP-05 ID is useless. The key is still primary.
- Not permanent. If your domain lapses or the file moves, your NIP-05 ID breaks. Your underlying key pair remains valid; you just need a new NIP-05 mapping.
- Not a centralised identity system. There is no NIP-05 authority. Every domain is its own registry. This is by design.
Before you start: what you need
1. Your hex public key
NIP-05’s nostr.json file uses hex-formatted keys exclusively. Most Nostr clients display the npub… Bech32 format, which is the same key encoded differently. You must convert to hex before writing your JSON file. Common ways:
- In Damus or Amethyst: go to Profile → Key Settings and look for a “copy public key hex” option.
- Using the open-source key-convertr tool:
key-convertr npub1abc…→ outputs hex. - Using nostrcheck.me/converter (browser-based, paste your npub, get hex).
Your hex public key is 64 lowercase hexadecimal characters. Example:
b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9
2. Your relay list
Optionally, include the relays where your notes are published. This helps clients find you faster. Use at least two or three reliable public relays. Common choices as of mid-2026:
wss://relay.damus.iowss://nos.lolwss://relay.primal.netwss://relay.nostr.band
If you run your own relay, include that URI too. See Running your own Nostr relay for a full walkthrough.
3. The nostr.json structure
The canonical JSON format, per NIP-05:
{
"names": {
"alice": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9",
"_": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"
},
"relays": {
"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9": [
"wss://relay.damus.io",
"wss://nos.lol",
"wss://relay.primal.net"
]
}
}
The "_" (underscore) name is a convention meaning “root user for this domain.” If you are the sole identity on your domain, adding "_": "your-hex-key" lets you use the identifier _@yourdomain.com — which some clients render as simply @yourdomain.com. You can include both the underscore entry and a named entry for the same key.
Critical: use hex format in the names object. An npub… key in this field will fail NIP-05 validation in every spec-compliant client.
Path 1: Static file (recommended for most setups)
The simplest and most reliable approach. Create the directory and file directly in your webroot; the webserver serves it without involving any application layer.
Step 1 — Create the file
mkdir -p /var/www/yourdomain.com/.well-known
cat > /var/www/yourdomain.com/.well-known/nostr.json <<'EOF'
{
"names": {
"_": "YOUR_64_CHAR_HEX_PUBKEY_HERE"
},
"relays": {
"YOUR_64_CHAR_HEX_PUBKEY_HERE": [
"wss://relay.damus.io",
"wss://nos.lol"
]
}
}
EOF
On a cPanel/Apache host, your webroot is typically /home/USERNAME/public_html/. Adjust the path accordingly.
Step 2 — Add the CORS header in Nginx
If your server runs Nginx as a front-end (or Nginx-only), add the following to your site’s server block:
location /.well-known {
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
}
# Ensure no redirects — NIP-05 spec forbids them at this endpoint
try_files $uri =404;
}
Reload Nginx after editing: nginx -s reload
Step 2 (alternative) — Add the CORS header in Apache .htaccess
If Apache is your primary server (or sits behind Nginx with no Nginx-level CORS), add this to your .htaccess or an Apache vhost block:
<Files "nostr.json">
Header set Access-Control-Allow-Origin "*"
Header set Content-Type "application/json"
</Files>
The mod_headers module must be enabled. On cPanel/AlmaLinux: a2enmod headers && systemctl reload httpd.
Step 3 — Verify
# CORS header present?
curl -sI "https://yourdomain.com/.well-known/nostr.json" | grep -i access-control
# JSON valid and reachable?
curl -s "https://yourdomain.com/.well-known/nostr.json" | python3 -m json.tool
# No redirect? (must return 200, not 301/302)
curl -sI "https://yourdomain.com/.well-known/nostr.json" | head -1
Then paste your NIP-05 identifier into the NostrDeck NIP-05 checker to confirm clients see it correctly.
Path 2: WordPress dynamic route
If you run a WordPress site and want to manage multiple Nostr identities from the admin, or want to update the key without touching the filesystem, you can serve /.well-known/nostr.json dynamically through WordPress.
The approach uses WordPress’s rewrite API to intercept the /.well-known/nostr.json request before WordPress renders a theme template.
Step 1 — Add a mu-plugin (or plugin)
Create /wp-content/mu-plugins/nip05-identity.php:
<?php
/**
* NIP-05 Nostr identity endpoint.
* Serves /.well-known/nostr.json with correct CORS headers.
*
* Edit $nip05_identities to add or update keys.
* Run wp rewrite flush --allow-root after first activation.
*/
// --- Configure your identities here ---
$nip05_identities = [
'names' => [
'_' => 'YOUR_64_CHAR_HEX_PUBKEY_HERE',
'youralias' => 'YOUR_64_CHAR_HEX_PUBKEY_HERE',
],
'relays' => [
'YOUR_64_CHAR_HEX_PUBKEY_HERE' => [
'wss://relay.damus.io',
'wss://nos.lol',
'wss://relay.primal.net',
],
],
];
// --- End configuration ---
add_action( 'init', function () {
add_rewrite_rule(
'^.well-known/nostr.json$',
'index.php?dc_nip05=1',
'top'
);
} );
add_filter( 'query_vars', function ( $vars ) {
$vars[] = 'dc_nip05';
return $vars;
} );
add_action( 'template_redirect', function () use ( $nip05_identities ) {
if ( ! get_query_var( 'dc_nip05' ) ) {
return;
}
// NIP-05 spec: no redirects, JSON only
status_header( 200 );
header( 'Content-Type: application/json; charset=utf-8' );
header( 'Access-Control-Allow-Origin: *' );
header( 'Cache-Control: public, max-age=3600' );
echo json_encode( $nip05_identities, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
exit;
} );
Step 2 — Flush rewrite rules
wp rewrite flush --allow-root
Run this from your WordPress root after placing the file. Without this step, the rewrite rule will not be registered in WordPress’s rule cache.
Step 3 — Ensure Nginx does not intercept first
If Nginx serves a cached or static version of /.well-known/, the WordPress PHP handler will never run. Confirm there is no Nginx try_files for /.well-known/nostr.json that resolves to a static file. If a static file exists at that path, it takes priority — which is fine if you want to use Path 1 instead.
The CORS header in this path is set by PHP, so no Nginx/Apache configuration change is needed for CORS specifically.
When to prefer this path
- You host multiple Nostr identities for your organisation and want a single admin-editable config.
- You plan to hook the key into a WordPress option or ACF field for UI editing.
- You do not have SSH/filesystem access to place static files.
Path 3: Dedicated lightweight service
For teams running infrastructure who want multi-domain or multi-user NIP-05 at scale, a small dedicated service is cleaner than embedding identity logic in a web application.
Options worth knowing
- planetary-social/nip05api — A minimal Go service. Accepts a config file of name-to-hex mappings, serves the
/.well-known/nostr.jsonendpoint with correct CORS. Suitable for running as a small Docker container or systemd service. - Static hosting with a JSON file — Netlify, Vercel, or any CDN that lets you drop a file at a known path and set a custom response header. The static file approach (Path 1) applies here; the CDN handles CORS via header rules.
- Managed NIP-05 services — Services like nostrplebs.com, nostrcheck.me, and similar offer verified NIP-05 identities on their domains if you prefer a custodial approach. The trade-off: you depend on their domain remaining active and controlled by someone else. Self-hosting eliminates that dependency.
Path 3 is overkill for a single-site operator. It makes sense when you are running a community, a business with multiple team members, or a relay operator assigning identities to subscribers.
CORS: why it matters and what to check
Nostr web clients (browser-based apps like Snort, Iris, Coracle) fetch nostr.json via JavaScript’s fetch() API. Browsers enforce the Same-Origin Policy: a script on snort.social cannot read a response from yourdomain.com unless that domain explicitly allows it with the correct header.
The required header:
Access-Control-Allow-Origin: *
This must appear in the response from your server, not just in a config file. Verify with:
curl -sI "https://yourdomain.com/.well-known/nostr.json" | grep -i access-control
Expected output: access-control-allow-origin: *
Common failures:
- Header missing entirely: The static file is served but no CORS rule is set. Add the Nginx or Apache block described in Path 1.
- Redirect at /.well-known/: Your server redirects
/.well-known/nostr.jsonto/well-known/nostr.json(missing the dot) or similar. NIP-05 spec says the endpoint MUST NOT redirect. Check your .htaccess for rules that might strip the leading dot from directory names. - Wrong Content-Type: Some servers serve the file as
text/plain. SetContent-Type: application/jsonto avoid client parsing errors. - Cloudflare cache: If a CDN layer cached an old response without the CORS header, purge the cache after adding the header rule.
Setting your NIP-05 identifier in your Nostr profile
Once your endpoint is live and verified, update your Nostr profile’s nip05 field to your identifier. The format:
youralias@yourdomain.com
Or, if using the underscore convention:
_@yourdomain.com
In most clients: Profile → Edit → Nostr Address / NIP-05 field → paste the identifier → save. The client will immediately attempt to verify by fetching your endpoint.
Honest caveats
NIP-05 is useful, not magic. A few things to hold clearly:
- Domain squatting is possible. If someone registers a similar-looking domain and sets up a plausible NIP-05 file, they can impersonate you with a different domain. NIP-05 does not prevent this — cryptographic key trust remains primary. Always verify the full domain, not just the local part.
- Clients vary in how prominently they surface verification. Some show a clear checkmark; others show nothing or show it only on hover. Do not assume all your followers will see a verification badge.
- Key rotation is disruptive. If you rotate your Nostr private key, you must update both your NIP-05 JSON file and your profile on relays. There is no automated migration path in the current spec.
- NIP-05 over HTTP (not HTTPS) is rejected by most clients. Your domain needs a valid TLS certificate.
D-Central’s Nostr presence
D-Central’s Nostr identity is being established as part of the sovereign communication layer of the Sovereign Stack. When active, the machine-readable endpoint will be at:
https://d-central.tech/.well-known/nostr.json
And the human-readable identifier will be _@d-central.tech.
This serves two purposes: demonstrating that a WordPress/Nginx stack can serve a correct NIP-05 endpoint (it can, as Path 1 and 2 above show), and participating in the Nostr ecosystem rather than only writing about it. The Nostr page at d-central.tech/nostr/ covers the broader ecosystem context.
Troubleshooting reference
| Symptom | Likely cause | Fix |
|---|---|---|
| Client shows “invalid NIP-05” | hex key missing or npub used instead | Convert npub to hex; paste hex in names |
| Client shows “fetch error” or CORS error in browser console | Missing Access-Control-Allow-Origin: * header |
Add CORS header in Nginx or Apache config |
| Endpoint returns 301 redirect | .htaccess or Nginx redirect rule for /.well-known/ |
Exempt the path from redirect rules; serve directly |
404 on /.well-known/nostr.json |
File not in webroot or WordPress is not routing the request | Path 1: confirm file exists. Path 2: flush rewrites. |
| WordPress Path 2 returns WordPress 404 page | Rewrite rules not flushed after adding mu-plugin | wp rewrite flush --allow-root |
| Verification worked, then broke | Domain renewed but TLS cert expired, or file deleted by deploy | Check cert validity; check webroot after deploys |
Frequently asked questions
- What is NIP-05 in Nostr?
- NIP-05 is a Nostr Improvement Proposal that defines how to map a Nostr public key to a DNS-based internet identifier — a human-readable email-shaped address like
alice@example.com. It is defined in the nostr-protocol/nips repository. - Can I set up NIP-05 without a dedicated server?
- Yes. Any hosting that lets you place a file at a known path and set a custom HTTP response header works. Static hosts (Netlify, Vercel, GitHub Pages with a custom domain) all support this. The file is a plain JSON file; no server-side scripting is required.
- Do I need to keep the nostr.json file updated?
- Only if you change your key or your relay list. Otherwise the file is static and requires no maintenance. The relay list is optional — clients can find you without it, just less efficiently.
- Is NIP-05 the same as “Nostr verification”?
- NIP-05 is the protocol-level mechanism. Each client decides how to display or label verified identifiers. Some show a checkmark; some do not. The underlying security guarantee is that whoever controls the domain controls the mapping — not a centralised authority.
- Can I use a subdomain for NIP-05?
- Yes.
alice@nostr.example.comis valid. Yournostr.jsonfile goes athttps://nostr.example.com/.well-known/nostr.json. Subdomains let you isolate the Nostr identity from your main domain if you prefer. - Does NIP-05 work if my site uses Cloudflare?
- Yes, but you need to ensure the CORS header is set at the origin and passes through Cloudflare. Cloudflare does not strip standard CORS headers. If you use Cloudflare’s Page Rules or Transform Rules to add headers, that also works. Purge the Cloudflare cache after any change to the endpoint.
- What is the underscore convention in nostr.json?
- The key
"_"in thenamesobject is a convention (not part of the formal spec) indicating the primary or root identity for the domain. The resulting identifier is_@yourdomain.com. Some clients render this as simplyyourdomain.com, which is visually clean for domain-level identities. - What happens if I lose my domain?
- Your NIP-05 identifier breaks: clients can no longer verify the mapping. Your private key and all your notes on relays remain intact. You will need to update your profile to a new NIP-05 identifier on whatever domain you control next, and communicate the change to your followers.
- Does NIP-05 prove I own the domain?
- Not cryptographically. It proves that whoever could write a file to
/.well-known/nostr.jsonon that domain at the time of setup chose to map that hex key. Trust in NIP-05 identifiers is domain trust, not key-signing proof of domain ownership. For deeper discussion of the trust model, the Block Engineering Blog post on NIP-05 trust is a useful reference.
For broader context on running sovereign communications infrastructure, see the Sovereign Stack guide, the Nostr hub, and how to run your own Nostr relay. For the wider picture of why self-hosting your identity matters, digital sovereignty in a Canadian context and sovereign computing 101 provide the framing.
Related products, repair, and setup paths
- Bitcoiner sovereignty hub
- the plebs sovereign stack
- Nostr for Bitcoiners
- run your own Nostr relay
- getting started with Meshtastic
- Bitcoin over Meshtastic mesh networks
- open-source hardware tools directory
- off-grid Bitcoin mining
Last reviewed June 15, 2026.
