Skip to content

Bitcoin accepted at checkout  |  Ships from Laval, QC, Canada  |  Expert support since 2016

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


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:

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:

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


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

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:


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:


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.com is valid. Your nostr.json file goes at https://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 the names object 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 simply yourdomain.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.json on 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.