Wildcard Subdomains For Local Development – 2017 Update

For years I’ve been using wildcard subdomains for local development rather than having to set up virtual hosts every time I want to spin up a new site. I was using a combination of nginx and dnsmasq to bring it all together. I’d use dnsmaq to make all calls to any subdomain on my development URL point to I’ve been using *.lee.test as my local development domain. So, if I wanted to spin up a new WordPress site for a new project I’d use the domain project-name.lee.test. Then I’d set nginx to use the subdomain as the web root directory for the project. So, I’d have a directory like /home/lee/sites/project-name.

This was all great until Ubuntu 16.10 came around and stopped using dnsmaq in favor of systemd-resolved. The dnsmasq packages are still available for Ubuntu 16.10, 17.04, and 17.10, but I really don’t want to have both dnsmaq AND systemd-resolver running at the same time. I also don’t want to replace systemd-resolver with dnsmasq. So… I have modified my workflow a bit with this bash script that writes domains directly to /etc/hosts. So, here’s how I do things now.

The Tools

Here’s a quick explanation of my local development setup.

First, I have created a directory called /home/lee/sites where I put all my web development projects / WordPress sites.

PHP: phpbrew
I’m using phpbrew fpm so I can flip around between various version of PHP. I do a lot of WordPress development and the official recommendation from WordPress.org is to use PHP 7, but a ton of WordPress sites are still running PHP 5.6 (and sometimes even older than that). So, I need to be able to flip things around so my local version of PHP matches the live site where I’ll be deploying the project. So, I use phpbrew and it’s excellent.

Web server: nginx
I’m using nginx as my web server and have it set up to pluck off the subdomain and use it for the root directory name for the project. Here’s what my sites-enabled/default nginx config file looks like.

server {
  listen 80 default_server;
  listen [::]:80 default_server;

  # Use the subdomain as document root
  server_name ~^(?.+)\.lee\.local$;
  root /home/lee/sites/$vhost;

  # Add index.php to the list if you are using PHP
  index index.php index.html index.htm index.nginx-debian.html;

  server_name _;

  location / {
    # First attempt to serve request as file, then
    # as directory, then append the query to index.php
    try_files $uri $uri/ /index.php?q=$uri&$args;

  # pass PHP scripts to FastCGI server
  location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    # Get PHP working with phpbrew php-fpm


I use WP-CLI to quickly spin up WordPress sites for local development. To make things quick and efficient, I’ve set up a config.yml file with some default settings for things like the database credentials, the WordPress admin user credentials, and a few tweaks to the wp-config.php file to enable debugging and to disable storing post revisions.

The config file lives in ~/.wp-cli/config.yml and looks like this.

core install:
  admin_user: dev-username
  admin_password: dev-password
  admin_email: your@email.com
  title: WordPress Development
core config:
  dbuser: db-username
  dbpass: db-password
  dbhost: localhost
  extra-php: |
    define( 'WP_DEBUG', true );
    define( 'WP_POST_REVISIONS', 0 );

Let me stress a the point that this is only for local development and not for a live, production server. You obviously need to be much more careful regarding your WordPress and database credentials when publishing live/production sites.

Spinning Up Sites

Here’s where it all comes together and gets awesome. With a single command I can spin up a fresh WordPress site in just a couple seconds. I wrote a little bash script that:

  1. Downloads the latest version of WordPress from WordPress.org
  2. Configures WordPress with my development info
  3. Creates the database for the WordPress site
  4. Installs WordPress on the domain I specify
  5. Adds the domain to /etc/hosts

Here’s the script:


# Usage:
# >wp-site.sh $subdomain [$port]

mkdir ~/sites/$1
cd ~/sites/$1
wp core download
wp core config --dbname=$1
wp db create

if [ $2 ]
    wp core install --url=$1.lee.test:$2
    wp core install --url=$1.lee.test

# Add domain to /etc/hosts
echo -e "\t$1.lee.test" | sudo tee -a /etc/hosts > /dev/null

The last line of the script is the new change now that I’m no longer using dnsmasq. That last line works like this:

  • echo -e evaluate the \t as a tab
  • "\t$1.lee.test" use the 1st argument passed to the script as the subdomain for the website. The string needs to be in double quotes to evaluate the $1. Otherwise it will just print a literal $1 in the /etc/hosts file which is not what we want.
  • | sudo tee -a /etc/hosts pipe the line we want to add to /etc/hosts to the tee command with sudo privileges because /etc/hosts is owned by root. Make sure to use -a to append the line to /etc/hosts, otherwise you’ll overwrite all the contents of the file which would be bad.
  • > /dev/null suppress the output of the tee command so we don’t have to see the output in the terminal.

I chose to use the tee command rather that applygin sudo to the entire echo statement so that the sudo permissions are applied to as little of the command as possible and the only part of the command that actually needs sudo privileges.

Example Usage

To see it all in action, suppose we wanted to spin up a site to work on cart66. This one command does the whole thing in about 3 seconds.

> wp-site.sh cart66
Downloading WordPress 4.8.2 (en_US)...
Using cached file '/home/lee/.wp-cli/cache/core/wordpress-4.8.2-en_US.tar.gz'...
Success: WordPress downloaded.
Success: Generated 'wp-config.php' file.
Success: Database created.
Success: WordPress installed successfully.

If you happen to see a line like this sh: 1: -t: not found in your output, don’t worry. It just means your php.ini does not have any value set for the sendmail_path. You can either ignore the line or edit your php.ini file and include the line sendmail_path = sendmail -t -i (which is the default value – you might want something different).

Leave a Reply

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