How to map domains in multisite with sunrise.php in WordPress

Contents

Overview — Domain mapping in WordPress Multisite with sunrise.php

This article explains, in exhaustive detail, how to map arbitrary domains to individual sites in a WordPress Multisite install using a custom sunrise.php implementation. It covers prerequisites, DNS and webserver configuration, the database mapping table, the exact sunrise.php code to use, SSL and redirect handling, cookie/login considerations, troubleshooting and advanced topics (wildcards, performance and security).

When to use sunrise.php

WordPress loads sunrise.php very early in the multisite bootstrap if you enable it in wp-config.php. That makes it the correct place to rewrite the incoming host to the primary domain for a blog and to implement custom domain -> blog_id resolution. Use it when you need:

  • To map multiple external domains to particular multisite blog IDs (example.com -> blog_id 3).
  • To implement custom canonical redirects from a mapped domain to the blogs primary domain.
  • To add domain mapping without relying on third-party plugins or when you need full control and minimal overhead.

Prerequisites and important notes

  • WordPress Multisite already created and working (subdomain or subdirectory type).
  • SSH or control panel access to edit files: wp-config.php and place wp-content/sunrise.php.
  • Ability to run SQL to create a mapping table, or run via phpMyAdmin / WP-CLI.
  • All mapped domains must resolve (DNS) to your multisite server IP (A records or CNAME depending on needs).
  • sunrise.php runs before WordPress initializes wpdb, so it must connect directly to MySQL (using DB constants from wp-config.php).
  • Test on staging first. A malformed sunrise can break public access if it exits unexpectedly or sends wrong headers.

Step 1 — Enable sunrise in wp-config.php

Open wp-config.php (root of WP install) and add these lines just before Thats all, stop editing or anywhere before multisite settings finalize:

/ Enable sunrise: allows wp-content/sunrise.php to run early /
define( SUNRISE, on )

/ Recommended: avoid cookie domain forcing — leave blank to support multiple domains /
define( COOKIE_DOMAIN,  )

Why define COOKIE_DOMAIN = ? If COOKIE_DOMAIN is set to a single domain, logins and admin cookies can break on mapped domains. Defining it as an empty string prevents WordPress from forcing cookies to one domain. This is recommended for domain-mapped multisite installs.

Step 2 — Create the domain mapping table

Create a database table to map external domains to blog IDs. Use the same table prefix you use in WordPress (default prefix is wp_). Example SQL:

CREATE TABLE wp_domain_map (
  id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  domain VARCHAR(255) NOT NULL,
  blog_id BIGINT UNSIGNED NOT NULL,
  active TINYINT(1) NOT NULL DEFAULT 1,
  redirect_to_primary TINYINT(1) NOT NULL DEFAULT 0,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY domain (domain)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

Columns explained:

  • domain: the hostname users type (no protocol, no path strip port if present).
  • blog_id: the numeric blog id from wp_blogs (the target site).
  • active: 1 means enabled, 0 disabled.
  • redirect_to_primary: when true, mapped requests will be 301-redirected to the blogs primary domain (normalizes canonical URL).

Step 3 — Place and implement wp-content/sunrise.php

Create the file wp-content/sunrise.php (exact filename). This file is executed before WordPress core sets up wpdb, so it must connect to MySQL using the DB_ constants that are already defined in wp-config.php. The example below is safe-by-default (fail open) and uses proper escaping. Adjust table names to match your table prefix.


Notes about the code above

  • The code intentionally fails open (returns silently) if DB is unreachable or mapping is not set — that avoids making the site completely inaccessible if sunrise fails.
  • Adjust mapping_table and wp_blogs_table to match your DB prefix.
  • The code queries mapping table directly using mysqli because wpdb hasnt been initialized yet when sunrise runs.
  • If you want more features (admin IP-only UI, automatic insertion of mapping rows when adding a site), you can build an admin plugin that inserts rows into the mapping table after site creation sunrise only needs read access.

Step 4 — Add mapping rows (examples)

Insert mappings that bind domains to blog IDs. Example SQL:

INSERT INTO wp_domain_map (domain, blog_id, redirect_to_primary, active)
VALUES
(example.com, 2, 0, 1),
(www.example.com, 2, 1, 1),
(clientdomain.net, 5, 0, 1)

To discover a blog_id for a site, query wp_blogs:

SELECT blog_id, domain, path FROM wp_blogs WHERE domain LIKE %your-network-domain%

Step 5 — DNS and webserver configuration

All mapped domains must resolve to the same server (or load balancer) that runs WordPress. Use A records for apex domains and CNAME for domains that point to another hostname if allowed. For a large list of unrelated domains pointing to the same host, create A records that point to your server IP.

Apache virtual host example

ltVirtualHost :80gt
    ServerName primary.example.com
    ServerAlias example.com www.example.com clientdomain.net anotherdomain.org
    DocumentRoot /var/www/html
    
        AllowOverride All
        Require all granted
    lt/Directorygt
    # Optional log directives...
lt/VirtualHostgt

Nginx server block example

server {
    listen 80
    server_name primary.example.com example.com www.example.com clientdomain.net
    root /var/www/html

    index index.php index.html

    location / {
        try_files uri uri/ /index.php?args
    }

    location ~ .php {
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock
        include fastcgi_params
        fastcgi_param SCRIPT_FILENAME document_rootfastcgi_script_name
    }
}

Step 6 — SSL (HTTPS) and Let’s Encrypt

If you need HTTPS on mapped domains, you must provision certificates for each domain. Two common approaches:

  • Use Let’s Encrypt to obtain certificates including all mapped domains (subject to rate limits). certbot supports multiple -d flags.
  • Use individual certificates for each domain, or a wildcard certificate when mapping many subdomains of the same domain.

Example certbot command (nginx plugin):

sudo certbot --nginx -d example.com -d www.example.com -d clientdomain.net

When using a reverse proxy or load balancer that terminates SSL, ensure proper forward headers are set so sunrise.php can detect HTTPS (see the redirect detection logic in the sunrise example).

Login, cookies and admin access

  • Keep define(COOKIE_DOMAIN, ) in wp-config.php so WP does not set cookies tied to only one domain.
  • When an admin visits a mapped domain, WordPress core will use the rewritten _SERVER[HTTP_HOST] to find the correct blog however, some links in the Network Admin may still use the network primary domain. If you need the Network Admin to be accessible on other domains, configure server aliasing for that host.
  • If you see login redirect loops, verify cookie domain, check that secure cookies are honored (HTTPS), and confirm session cookies are not blocked by browser privacy settings. Clear browser cookies and test.

Permalinks, resources, and mixed content

A common 404 or broken stylesheet problem after mapping usually stems from hard-coded absolute URLs inside posts, themes, or plugin settings. Search-and-replace old domains using WP-CLI or a safe serialized-aware tool. Also ensure any resource URLs (CSS, JS) use relative URLs or the correct mapped domain.

Troubleshooting — common issues and fixes

  1. Mapping not working (site 404):

    • Verify the domain exists in the mapping table and is active.
    • Double-check the blog_id exists in wp_blogs and that the blogs domain/path are correct.
    • Ensure DNS for the mapped domain points to your server and webserver config includes the domain (ServerAlias or server_name).
  2. Login redirects to the network primary domain:

    • Make sure COOKIE_DOMAIN is empty in wp-config.php.
    • Check cookie and secure cookie handling (is HTTPS detected properly?).
  3. Broken admin (Network Admin links to wrong domain):

    • Network Admin is tied to the network primary domain. Use the primary domain for network administration or add aliases in your server configuration.
  4. Mixed content errors:

    • Serve assets using HTTPS update DB-stored asset URLs or use a plugin to force HTTPS for resources.
  5. Performance and caching problems:

    • Varnish and other caches must vary cache by host header otherwise, one domains content may be served for another.
    • Configure your caching layer to include the host in cache key.

Security considerations

  • Do not output raw DB errors to the browser in sunrise.php. The example fails silently on DB errors to avoid disclosure.
  • Sanitize inputs (we used mysqli_real_escape_string). If you expand logic, use prepared statements to avoid injection.
  • If your DB server is remote, secure the connection (SSL/TLS) between the web server and the DB host.
  • Limit access to the mapping admin UI (if you build one) to network super admins and trusted administrators.

Advanced: wildcards and subdomain mapping

  • Wildcards: If you want all subdomains of a domain (like client.example.com) to map to a single blog you can:

    1. Use a wildcard DNS A record (.example.com -> server IP).
    2. Modify sunrise.php to test wildcard matches (e.g. check for pattern .example.com in your mapping table or match by suffix).
  • Path-based mapping: Mapping an external domain to a subdirectory path (example.com -> primary.example.com/blogpath/) requires additional care: you must preserve REQUEST_URI and possibly rewrite PATH_INFO so WordPress resolves the subdirectory. This is more complex and often easier solved by mapping to the blogs root or using reverse proxy rules.

Maintenance and operational tips

  • Back up your database before changing mapping rows or modifying sunrise.php.
  • Test mapping changes using curl or via a temporary hosts file entry on your machine to point the domain to your server before updating DNS.
  • Log mapping hits if you want an audit trail do not log sensitive data. You can extend sunrise.php to write to a log file with strict permissions.
  • If you maintain a large list of domains, consider a small admin plugin to manage the mapping table with sanity checks and validation.

Complete example checklist (summary)

  1. Create multisite and confirm default installs work.
  2. Edit wp-config.php: add define(SUNRISE,on) and define(COOKIE_DOMAIN,)
  3. Create mapping table wp_domain_map (or similar) and insert rows mapping hostnames to blog IDs.
  4. Create wp-content/sunrise.php using the sample code above update table names/prefix as required.
  5. Ensure DNS for all mapped domains resolves to your server IP.
  6. Update webserver vhost / server_name to include mapped domains (ServerAlias / server_name list) or use a catch-all and rely on mapping in sunrise.
  7. Provision SSL certificates for each domain or use a wildcard or SAN certificates.
  8. Test mapping, login and front-end on each mapped domain check cookies and permalinks.
  9. Monitor logs and verify caching layer respects the Host header.

Appendix — Useful SQL and commands

Find blog_id for a site (example):

SELECT blog_id, domain, path FROM wp_blogs WHERE domain = primary.example.com

Insert a mapping:

INSERT INTO wp_domain_map (domain, blog_id, redirect_to_primary, active)
VALUES (mycustomdomain.com, 3, 0, 1)

Obtain certificates with certbot for multiple domains:

sudo certbot --nginx -d example.com -d www.example.com -d mycustomdomain.com

Closing operational notes

This approach (custom sunrise.php mapping table) is lightweight and avoids a large third-party plugin dependency while giving you full control. Be disciplined about testing and backups before production changes. When scaling to hundreds or thousands of domains consider a more robust management UI, automated certificate provisioning workflows, and a caching infrastructure that keys by host.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *