Roots Discourse

Not Sure How To Get Multisite Functional in Dev Environment

I have been using trellis for single site instances for a while and I absolutely love it, but I have been tasked to take four individual sites and bring them into a WP multisite installation. I feel as though I have read the MultiSite Documentation (https://roots.io/trellis/docs/multisite/) and run through the forums feeling quite lost in the process. As a junior developer, I totally get if no one is able to answer his question or if there is answer already out there that I haven’t explored. I just need a little help breaking down this problem.

As the Multisite documentation states, I need to inject a bit of PHP into the application.php file to activate multisite, which I have done, but now comes the configuration of the group_vars (so I believe). Given the wording of the documentation it sounds like I need to create a multisite “entry” for each of my sites. I am planning on deploying four sites for a company with four different domains. Let’s say my developer domains are environment1.test, environment2.test, environment3.test, and environment4.test. I do not want to deploy to subdomains as that is not the end goal.

Given the wording of documentation, do I need to create a record (in group_vars/development/wordpress_sites.yml) for each of these domains that looks like this (creating four records with the altered development domain)?

environment1.test:
site_hosts:
  - canonical: environment1.test
    redirects:
      - www.environment1.test
local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root)
admin_email: #Insert Address Here
multisite:
  enabled: true
  subdomains: false # Set to true if you're using a subdomain multisite install
ssl:
  enabled: true
  provider: self-signed
cache:
  enabled: false
env:
  domain_current_site: environment1.test

If so, I have tried this approach and have not been able to achieve a successful multisite deployment.

Lastly, do I need to have a unique bedrock directory for each of these sites? If so, doesn’t that technically defeat the purpose of the multisite installation?

I am quite new if you cannot tell, and I could use a bit of coaching here. Anything that you are willing to pass along, I will gladly take. You’d be paying it forward to someone who is super green on this system…

Thank you in advance!

Ok, so from the sounds of it, you want to host a Bedrock multisite network where the domains are all unique (not subdomains or subdirectories). For multisite installs WordPress gives you two options, subdomains or subdirectories. In order to do domain mapping — i.e. using custom domains for each site — we need to go with the subdomain option. This means we just need one Bedrock install. When you initially create a site in the network admin, you will need to create the domain as: <domain-i-want>.<main-domain>.<tld>, but after the site is created, WordPress lets you change that domain to <domain-i-want>.<tld>.

Here’s an example Trellis config for a couple of environments:

trellis/group_vars/production/wordpress_sites.yml (production sites)
wordpress_sites:
  example.com:
    site_hosts:
      - canonical: example.com
        redirects:
          - www.example.com
      - canonical: client.com  # ← Add your custom domains like this.
          - www.client.com
    local_path: ../site
    repo: git@github.com:agency/wp-network.git
    repo_subtree_path: site
    branch: master
    multisite: # ← Multisite config stuff for Trellis
      enabled: true
      subdomains: true
    ssl:
      enabled: true
      provider: letsencrypt
      # Disable HSTS – I’m doing this since I don’t want HSTS on all of the domains.
      hsts_max_age: 0
    cache:
      enabled: false
    domain_current_site: example.com # ← This is the domain for the network admin.
trellis/group_vars/development/wordpress_sites.yml (development sites)
wordpress_sites:
  example.com:
    site_hosts:
      - canonical: example.test
        redirects:
          - www.example.test
      - canonical: client.test
        redirects:
          - www.client.test
    local_path: ../site
    admin_email: admin@example.test
    multisite:
      enabled: true
      subdomains: true
    ssl:
      enabled: false
      provider: self-signed
    cache:
      enabled: false

In development, since you are using multisite, Trellis will using the Vagrant Landrush plugin instead of the host manager to set the development domains. You do need to ensure you have the plugin installed first. You can do so with: vagrant plugin install landrush. Vagrant Landrush creates entries in your host machine’s DNS resolver for each domain you specify. You can double check it’s working by checking the contents of the /etc/resolver directory. After running vagrant up (wait to do this though, there is more important config below), you should see:

/etc/resolver
├── client.test
└── example.test

Ideally, the plugin should just work and you shouldn’t need to manually do this, but I found it helpful to know what magic was occurring and how I could debug it.

Now, the code that tells WordPress to be multisite needs to go into the Bedrock application config. I’ve included the actually snippet of code (for copy & pasting) and then a diff demonstrating where that code should (probably) be added:

site/config/application.php (snippet)
// Allow Multisite
Config::define('WP_ALLOW_MULTISITE', true);

/**
 * Multisite
 */
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', true);
define('DOMAIN_CURRENT_SITE', env('DOMAIN_CURRENT_SITE'));
define('PATH_CURRENT_SITE', env('PATH_CURRENT_SITE') ?: '/');
define('SITE_ID_CURRENT_SITE', env('SITE_ID_CURRENT_SITE') ?: 1);
define('BLOG_ID_CURRENT_SITE', env('BLOG_ID_CURRENT_SITE') ?: 1);

Config::define('COOKIE_DOMAIN', $_SERVER['HTTP_HOST']);
site/config/application.php (full)
  <?php
  /**
   * Your base production configuration goes in this file. Environment-specific
   * overrides go in their respective config/environments/{{WP_ENV}}.php file.
   *
   * A good default policy is to deviate from the production config as little as
   * possible. Try to define as much of your configuration in this file as you
   * can.
   */
  
  use Roots\WPConfig\Config;
  
  /** @var string Directory containing all of the site's files */
  $root_dir = dirname(__DIR__);
  
  /** @var string Document Root */
  $webroot_dir = $root_dir . '/web';
  
  /**
   * Expose global env() function from oscarotero/env
   */
  Env::init();
  
  /**
   * Use Dotenv to set required environment variables and load .env file in root
   */
  $dotenv = Dotenv\Dotenv::create($root_dir);
  if (file_exists($root_dir . '/.env')) {
      $dotenv->load();
      $dotenv->required(['DB_NAME', 'DB_USER', 'DB_PASSWORD', 'WP_HOME', 'WP_SITEURL']);
  }
  
  /**
   * Set up our global environment constant and load its config first
   * Default: production
   */
  define('WP_ENV', env('WP_ENV') ?: 'production');
  
  /**
   * URLs
   */
  Config::define('WP_HOME', env('WP_HOME'));
  Config::define('WP_SITEURL', env('WP_SITEURL'));
  
  /**
   * Custom Content Directory
   */
  Config::define('CONTENT_DIR', '/app');
  Config::define('WP_CONTENT_DIR', $webroot_dir . Config::get('CONTENT_DIR'));
  Config::define('WP_CONTENT_URL', Config::get('WP_HOME') . Config::get('CONTENT_DIR'));
  
  /**
   * DB settings
   */
  Config::define('DB_NAME', env('DB_NAME'));
  Config::define('DB_USER', env('DB_USER'));
  Config::define('DB_PASSWORD', env('DB_PASSWORD'));
  Config::define('DB_HOST', env('DB_HOST') ?: 'localhost');
  Config::define('DB_CHARSET', 'utf8mb4');
  Config::define('DB_COLLATE', '');
  $table_prefix = env('DB_PREFIX') ?: 'wp_';
  
  /**
   * Authentication Unique Keys and Salts
   */
  Config::define('AUTH_KEY', env('AUTH_KEY'));
  Config::define('SECURE_AUTH_KEY', env('SECURE_AUTH_KEY'));
  Config::define('LOGGED_IN_KEY', env('LOGGED_IN_KEY'));
  Config::define('NONCE_KEY', env('NONCE_KEY'));
  Config::define('AUTH_SALT', env('AUTH_SALT'));
  Config::define('SECURE_AUTH_SALT', env('SECURE_AUTH_SALT'));
  Config::define('LOGGED_IN_SALT', env('LOGGED_IN_SALT'));
  Config::define('NONCE_SALT', env('NONCE_SALT'));
  
  /**
   * Custom Settings
   */
  Config::define('AUTOMATIC_UPDATER_DISABLED', true);
  Config::define('DISABLE_WP_CRON', env('DISABLE_WP_CRON') ?: false);
  // Disable the plugin and theme file editor in the admin
  Config::define('DISALLOW_FILE_EDIT', true);
  // Disable plugin and theme updates and installation from the admin
  Config::define('DISALLOW_FILE_MODS', true);
  // Allow Multisite
+ Config::define('WP_ALLOW_MULTISITE', true);
+
+ /**
+ * Multisite
+ */
+ define('MULTISITE', true);
+ define('SUBDOMAIN_INSTALL', true);
+ define('DOMAIN_CURRENT_SITE', env('DOMAIN_CURRENT_SITE'));
+ define('PATH_CURRENT_SITE', env('PATH_CURRENT_SITE') ?: '/');
+ define('SITE_ID_CURRENT_SITE', env('SITE_ID_CURRENT_SITE') ?: 1);
+ define('BLOG_ID_CURRENT_SITE', env('BLOG_ID_CURRENT_SITE') ?: 1);
+  
+ /**
+  * Use the current HTTP host as the cookie domain. This ensures cookies and
+  * nonces are using the correct domain for the corresponding site. Without
+  * this, logins, REST requests, Gutenberg AJAX requests, and other actions
+  * which require verification will not work.
+  */
+ Config::define('COOKIE_DOMAIN', $_SERVER['HTTP_HOST']);
  
  /**
   * Debugging Settings
   */
  Config::define('WP_DEBUG_DISPLAY', false);
  Config::define('SCRIPT_DEBUG', false);
  ini_set('display_errors', 0);
  
  $env_config = __DIR__ . '/environments/' . WP_ENV . '.php';
  
  if (file_exists($env_config)) {
      require_once $env_config;
  }
  
  Config::apply();
  
  /**
   * Bootstrap WordPress
   */
  if (!defined('ABSPATH')) {
      define('ABSPATH', $webroot_dir . '/wp/');
  }

Note, I’m using define() rather than \Roots\Config::define() since Trellis will temporarily add these constants when it deploys. The latter will throw an error that stops the deploy while the former is silent (even though constants are being redefined).

Something else to note, is that I am setting the COOKIE_DOMAIN to $_SERVER['HTTP_HOST']. First of all, this is to avoid the login loop issue that happens in multisite installs. Often, guides to fix that issue will tell you to leave it as define('COOKIE_DOMAIN', ''), however, this causes issues with REST requests, which use cookie nonces to verify requests. If the domain of the current sub site (unique from the network domain) is not found the verification will fail (and Gutenberg or whatever won’t save), so setting this $_SERVER['HTTP_HOST'] is the best way to remedy the issue.

As far as I know, that’s really all that’s required for a multisite install using custom domains. If you have issues, a good debugging step is to check what is set for the domains column in the wp_blogs table and the rows for the siteurl and home option_values in the wp_<#>_options tables.

Let me know if there’s any issues you encounter as there may have been something that I’ve missed.

Update:

As the multisite docs note, I found adding the following to the Vagrantfile (line 62 or so) was important, otherwise, the VM couldn’t download anything.

config.landrush.guest_redirect_dns = false
3 Likes

@knowler, this reply simply rocked! Just checked out your website, and I can’t buy you a coffee, but I sure wish I could. The fact that you took the time to write and explain the entire process really helped. If you have any tips on how I can pass you some kudos, I’ll take those too! :grin:

I may have questions about deployment a little bit later, so I might keep this thread open. Thanks again @knowler

1 Like

No problem. I’m neck deep in multisite right now so taking the time to explain it helps me solidify understanding. Also, thanks for the heads up on the coffee thing not working, I was able to fix it. :+1: Roots does have a Patreon if you want to check that out as well.

1 Like

Alright, @knowler, I am back for a round two if you are willing to give me a little extra push.

I am doing my best to get this site deployed into a staging environment, and while I know I can have as many sub levels in a domain as I wish (See https://serverfault.com/questions/278295/subdomain-of-a-subdomain), I am not sure how to go about setting up such a push using WP Multisite and Roots. My hope is to have something like “subdomain-site-title**.client-name.domain.**com” This may seem like a bit much, but my company traditionally deploys our staging servers to a subdomain of our company’s domain to make it easy for clients to see out work. In short, how do I actually go about making subdomains of subdomains using Muti-Site and Roots.

(Please also let me know if this flies too far off the topic of the original question, and if I need to open a up a new ticket, I just wanted to reach out you specifically since you were so instrumental last time)

P.S: I left you a little coffee money for your help last time, and am considering the Patreon donation in my regular expenses for next month.

Hm, interesting predicament. Here are some ideas off the top of my head:

  1. Staging site in existing multisite install: I believe you should be able to set up staging domains staging.<client>.<tld> using the same method of setting custom domains above. You’d just need to add those domains to the site as well.
    • A downside of this is then your multisite would be serving twice as many sites and if you were copying the DB table/uploads between staging/production sub-sites that’d be a lot of extra data.
    • You won’t have a true staging environment for testing your code (i.e. if something breaks, all of your sites break).
  2. Staging site on staging multisite install: You could setup an identical multisite specifically for staging and have the staging subdomains point to it.
    • This would be a true staging environment.
    • This would be more to manage though.
  3. Staging site only prior to launch: You don’t need to map the custom domain until you launch, so you can use that as a “staging” environment until then.

Personally, I’d lean towards option 2, but maybe 3 if I was lazy. 1 seams flawed and sketchy.

I think that if you are doing multisite, you must count the cost of needing to manage a more difficult staging situation if you want to provide staging. In the end, it might be easier to just have one site per WP install (I prefer this over multisite). I think it could be do-able with some practice and maybe a plugin to help migrate the DB between environments.

@knowler, I have tried option three as you had mentioned: backfired spectacularly and led to a “too many redirections” error in the browser, and I agree option two is probably my best bet, I just have to figure out how to navigate that solution.

I might come back to the client and see whether or not multi-site is something we are particularly attached to. The idea sounds great in theory, but it may be more work that is truly helpful or manageable.

I cannot thank you enough @knowler for all of your help. You have been instrumental in this process as I learn!

1 Like