| | |

Host Multiple Domains with Nginx on Ubuntu 22.04.

In a previous article, we showed you how to set up a full LEMP stack on Ubuntu 22.04 with the latest stable version of Nginx, MariaDB and PHP, which will serve as the foundation for a reliable and performance-focused hosting platform.

Nginx is a fast, lightweight and powerful web server that can also be used for reverse proxy, load balancing and caching. It is very performant and uses relatively few resources, making Nginx an ideal choice for a shared hosting platform, even on a low budget VPS with only one vCPU and a few Gigabytes of RAM.

In this article, we will learn how to configure Nginx to host multiple websites on the same server using the LEMP stack created earlier.

This tutorial requires you to be logged in as root, so switch to root user if you are not already.

sudo -i

To keep everything neatly separated, we will start by creating a new directory “/var/www/virtual” that will store all web directories and files from each of the individual hosted domains.

mkdir -p /var/www/virtual && chown -R www-data. /var/www/virtual

For security reasons, it is recommended that strict SNI compliance be enforced when using shared hosting, with requests for domain names not hosted on the web server or the IP address of both “IPv4” and “IPv6,” from the server itself being rejected for both “HTTP” and “HTTPS,” as discussed in a previous article.

To accomplish this, it is necessary to edit the default.conf file.

cat > /etc/nginx/conf.d/default.conf << EOF
server {
    listen   80 default_server;
    listen [::]:80 default_server;
    listen 443 default_server http2 ssl;
    listen [::]:443 default_server http2 ssl;

    ssl_reject_handshake on;

    server_name _;

    return 444;

To comply with current guidelines and security standards, a new snippet “security-headers.conf” is created with all the necessary security headers that can be easily added to a server block.

cat > /etc/nginx/global/security-headers.conf << EOF
add_header X-Frame-Options "SAMEORIGIN";
add_header 'Referrer-Policy' 'strict-origin';
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header Content-Security-Policy "default-src https: data: 'unsafe-inline' 'unsafe-eval'" always;
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";

To enable http-01 validation over HTTP to request SSL certificates, an additional snippet “acme.conf” must be created that provides an alias for the “.well-known/acme-challenge/”.

cat > /etc/nginx/global/acme.conf << EOF
location /.well-known/acme-challenge/ {
    alias /var/www/acme/.well-known/acme-challenge/;

Of course, we also need to make sure that the corresponding directory exists and is created.

mkdir -p -m 750 /var/www/acme/.well-known/acme-challenge && chown -R www-data. /var/www/acme

Now that all the modifications have been made, it is time to test the configuration and correct any errors before starting Nginx.

nginx -t

Which should give you the following output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If no errors occur, restart Nginx to implement the new configuration.

systemctl restart nginx

Now that the basics are in place we can start creating the first “virtual host” using the domain “example.com” as an example. The following steps should be used for all successive domains we wish to host on the server.

Create a variable “DOMAIN” with the value of the name of the domain for which we are creating the “virtual host”, in our case “example.com”. Make sure the necessary DNS records are in place so that the domain points to the server’s IP address for both “IPv4” and “IPv6.”

export DOMAIN="example.com"

Check if the value of the variable is correct.

echo $DOMAIN

Which should give you the domain name you entered.


If you want your website to also be available under the “www” (www.example.com) subdomain, it must be added separately to the “server_name” directive of the Nginx server block. You can also add other (sub)domains (e.g. example.net www.example.net example.eu www.example.eu sales.example.com). These are declared in the variable “ALT_DOMAINS”.

export ALT_DOMAINS="www.example.com example.net www.example.net example.eu www.example.eu sales.example.com"

If you do not have alternate domains, it is important to declare the variable “ALT_DOMAINS” anyway to prevent it from containing data from a previous process. In this case, we leave everything between the braces empty.

export ALT_DOMAINS=""

We merge the the two variables into a new variable “DOMAINS”.

if [ -z "$ALT_DOMAINS" ]; then DOMAINS=$DOMAIN; else export DOMAINS="$DOMAIN " && export DOMAINS+=$ALT_DOMAINS; fi

Create a new directory that will serve as the web root for the new virtual host.

mkdir -p /var/www/virtual/$DOMAIN/htdocs

Create a new server block.

cat > /etc/nginx/sites-available/$DOMAIN <<EOF
server {
  listen 80;
  listen [::]:80;
  server_name $DOMAINS;
  root /var/www/virtual/$DOMAIN/htdocs/;
  index index.php index.html index.htm;
  include global/acme.conf;

  access_log /var/log/nginx/$DOMAIN-access.log;
  error_log /var/log/nginx/$DOMAIN-error.log;

  location / {
    try_files \$uri \$uri/ =404;

  location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    include global/fastcgi-php.conf;
    include global/security-headers.conf;

  location ~ /\.ht {
    deny all;

To enable the new server block file, a symbolic link must be created from this file to the “sites-enabled” directory, from which Nginx reads during startup.

ln -s /etc/nginx/sites-available/$DOMAIN /etc/nginx/sites-enabled/

In order for our web server to actually respond when the url “http://example.com” is entered into a browser, a simple index.html file must be created to serve as a placeholder page.

cat > /var/www/virtual/$DOMAIN/htdocs/index.html <<EOF
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<title>Placeholder for $DOMAIN</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Cache-Control" content="no-cache">

<body bgcolor=white>
<br /><br />
<div align=center>
<font face="Arial, Helvetica">
Placeholder for <b>$DOMAIN</b>

Finally, the following commands ensure that all created folders and files are given the correct owner and permissions.

chown -R www-data. /var/www/virtual/$DOMAIN &&\
find /var/www/virtual/$DOMAIN/ -type d -exec chmod 750 {} \; &&\
find /var/www/virtual/$DOMAIN/ -type f -exec chmod 640 {} \;

To check that no errors have crept into our configuration, we will test it before restarting Nginx.

nginx -t

Which should give you the following output:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If no errors occur, restart Nginx to implement the new configuration.

systemctl restart nginx

If all went well and you enter “http://example.com” into a browser you should get the “pageholder” page as shown in the image below.

Placeholder for example.com

Repeat these steps for all subsequent domains you want to host on this server.

Note that for now the page is only available in “HTTP” and not yet in “HTTPS.” In a future article, we will see how to request an SSL certificate and force the website to connect only via “HTTPS.”

Similar Posts