yum upgrades for production use, this is the repository for you.
Active subscription is required.
Setting up an NGINX virtual host configuration allows you to host multiple domains on a single server, maximizing your server resources and simplifying management. This comprehensive guide explains everything you need to know about NGINX virtual hosts (server blocks), from basic configuration to advanced wildcard subdomains.
What Is an NGINX Virtual Host?
An NGINX virtual host, technically called a “server block,” is a configuration that allows NGINX to serve different content based on the requested domain name. While Apache calls them “virtual hosts,” NGINX uses the term “server blocks” – but they accomplish the same goal: hosting multiple websites on one server.
When a request arrives at your server, NGINX examines the Host header and matches it against the server_name directives in your configuration. Based on this match, NGINX serves content from the appropriate root directory and applies the corresponding configuration.
Why Use Virtual Hosts?
Virtual hosting provides several benefits:
- Cost efficiency: Run multiple websites on a single server
- Resource optimization: Share server resources across multiple domains
- Simplified management: Centralize all your web applications
- IP conservation: No need for separate IP addresses per domain
Prerequisites
Before configuring NGINX virtual hosts, ensure you have:
- A server running Rocky Linux, AlmaLinux, RHEL, or another Enterprise Linux distribution
- Root or sudo access
- NGINX installed and running
- Your domain names pointed to your server’s IP address
Installing NGINX on Enterprise Linux
If NGINX is not installed, install it using dnf:
dnf install nginx
Enable and start NGINX:
systemctl enable --now nginx
Verify the installation:
nginx -v
Opening Firewall Ports
Allow HTTP and HTTPS traffic through the firewall:
firewall-cmd --permanent --add-service=http --add-service=https
firewall-cmd --reload
Understanding the NGINX Configuration Structure
For managing multiple virtual hosts, the recommended approach uses the sites-available and sites-enabled pattern:
/etc/nginx/nginx.conf– Main configuration file/etc/nginx/conf.d/– Global configuration snippets/etc/nginx/sites-available/– All virtual host configurations (inactive)/etc/nginx/sites-enabled/– Symlinks to active configurations
Create these directories if they don’t exist:
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
Add the following line to your nginx.conf inside the http block (before the closing brace):
include /etc/nginx/sites-enabled/*.conf;
This pattern allows you to easily enable or disable sites by creating or removing symlinks, without deleting configuration files.
Basic NGINX Virtual Host Configuration
Let’s create your first virtual host. Each virtual host needs:
- A dedicated system user for the website
- A server block configuration
- A document root directory with proper permissions
Step 1: Create the Website User
Each website should run under its own dedicated user account for security isolation. Never use nginx, www-data, or root as website owners. For detailed explanation of proper permissions, see our guide on NGINX and PHP-FPM permissions.
Create a dedicated user for the website:
useradd example
passwd example
Add the NGINX user to the website user’s group so it can read files:
usermod -aG example nginx
Step 2: Create the Document Root
Create a directory structure for your website:
mkdir -p /var/www/example.com/html
Set the correct ownership (website user owns the files):
chown -R example:example /var/www/example.com
Set secure permissions:
chmod -R u=rwX,g=rX,o= /var/www/example.com
This translates to: owner can read/write, group (nginx) can read, others have no access.
Create a test page:
sudo -u example bash -c 'echo "<html><body><h1>Welcome to example.com</h1></body></html>" > /var/www/example.com/html/index.html'
Step 3: Configure SELinux Context
On SELinux-enabled systems (default on Enterprise Linux), set the correct security context:
semanage fcontext -a -t httpd_sys_content_t "/var/www(/.*)?"
restorecon -Rv /var/www
Without this step, NGINX returns a 403 Forbidden error because SELinux prevents the web server from accessing files with incorrect contexts.
Step 4: Create the Server Block Configuration
Create the configuration file in sites-available:
vi /etc/nginx/sites-available/example.com.conf
Add the following configuration:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Step 5: Enable the Site
Create a symlink to enable the site:
ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
Step 6: Test and Reload NGINX
Always test your configuration before applying changes:
nginx -t
You should see:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Apply the configuration:
systemctl reload nginx
Hosting Multiple Domains
To host additional domains, repeat the process for each website with its own user:
Create the user and directory:
useradd example_org
usermod -aG example_org nginx
mkdir -p /var/www/example.org/html
chown -R example_org:example_org /var/www/example.org
chmod -R u=rwX,g=rX,o= /var/www/example.org
restorecon -Rv /var/www/example.org
Create the server block configuration at /etc/nginx/sites-available/example.org.conf:
server {
listen 80;
listen [::]:80;
server_name example.org www.example.org;
root /var/www/example.org/html;
index index.html;
access_log /var/log/nginx/example.org.access.log;
error_log /var/log/nginx/example.org.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Enable the site and reload:
ln -s /etc/nginx/sites-available/example.org.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Understanding the server_name Directive
The server_name directive determines which server block handles a request. NGINX supports several matching patterns as documented in the official NGINX documentation:
Exact Names
The most common and fastest matching method:
server_name example.com www.example.com;
You can specify multiple names separated by spaces.
Wildcard Names
Match any subdomain with a leading asterisk:
server_name *.example.com;
This matches www.example.com, api.example.com, blog.example.com, and any other subdomain.
For trailing wildcards:
server_name example.*;
This matches example.com, example.org, example.net, etc.
Regular Expressions
For complex matching patterns, use regex (prefix with ~):
server_name ~^(?<subdomain>.+)\.example\.com$;
This captures the subdomain into a variable you can use elsewhere in the configuration.
Server Name Matching Priority
When multiple server blocks could match a request, NGINX follows this priority order:
- Exact name match – Fastest, highest priority
- Longest wildcard starting with asterisk – e.g.,
*.example.com - Longest wildcard ending with asterisk – e.g.,
mail.* - First matching regular expression – In configuration order
- Default server – Fallback when no match found
This matching algorithm is similar to how NGINX location blocks work with their own priority system. Understanding both systems is essential for predictable NGINX behavior.
The default_server Directive
The default_server parameter designates which server block handles requests that don’t match any server_name:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
In this example, unmatched requests receive no response (connection closed). The underscore (_) is a convention indicating “catch-all” – it’s not a special NGINX syntax but simply an invalid domain name that never matches real requests.
Why Use default_server?
Without an explicit default server, NGINX uses the first server block in configuration order. This can lead to unexpected behavior. Common default server uses include:
- Blocking invalid requests: Return 444 to close connections
- Redirecting to a main site: Send all traffic to your primary domain
- Displaying a default page: Show a generic landing page
Save this as /etc/nginx/sites-available/default.conf and enable it:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
Configuring Wildcard Subdomains
Wildcard server blocks handle all subdomains without creating individual configurations:
server {
listen 80;
listen [::]:80;
server_name *.example.com;
root /var/www/subdomains/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Serving Different Content Per Subdomain
Use the $host variable to serve subdomain-specific content:
server {
listen 80;
listen [::]:80;
server_name *.example.com;
set $subdomain "";
if ($host ~ ^(.+)\.example\.com$) {
set $subdomain $1;
}
root /var/www/subdomains/$subdomain/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Create directories for each subdomain:
mkdir -p /var/www/subdomains/{blog,shop,api}/html
Advanced: Regex Server Names with Named Captures
For sophisticated routing, use regex with named captures:
server {
listen 80;
listen [::]:80;
server_name ~^(?<subdomain>.+)\.app\.example\.com$;
root /var/www/apps/$subdomain/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
The captured $subdomain variable is available throughout the configuration. This technique works well with NGINX rewrite rules for complex URL transformations.
Virtual Host Best Practices
Use sites-available and sites-enabled
This pattern provides several advantages:
- Easy enable/disable: Toggle sites with symlinks without deleting configs
- Clean organization: All configs in one place, active ones linked
- Safe testing: Create configs without activating them
To disable a site:
rm /etc/nginx/sites-enabled/example.com.conf
systemctl reload nginx
To re-enable:
ln -s /etc/nginx/sites-available/example.com.conf /etc/nginx/sites-enabled/
systemctl reload nginx
Dedicated Log Files
Configure separate access and error logs for each virtual host:
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
This separation helps with debugging and traffic analysis.
Consistent Directory Structure
Maintain a standard directory layout:
/var/www/
├── example.com/
│ └── html/
├── example.org/
│ └── html/
SELinux Considerations
On Enterprise Linux with SELinux enabled:
- Always set correct file contexts for web content
- Use
httpd_sys_content_tfor static files - Use
httpd_sys_rw_content_tfor directories needing write access
Check current contexts:
ls -laZ /var/www/
Security Hardening
For production environments, apply these essential security configurations:
Disable Version Disclosure: Prevent NGINX from revealing its version in error pages and response headers by adding this to your http block in nginx.conf:
http {
server_tokens off;
# ... other directives
}
Enable HTTPS: For complete SSL/TLS configuration, see our NGINX TLS 1.3 hardening guide. Here’s a production-ready HTTPS virtual host configuration:
server {
listen 80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS - force HTTPS for one year
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
This configuration includes:
- TLSv1.2 and TLSv1.3 only – older protocols are vulnerable to attacks
- Strong cipher suites – AEAD ciphers that provide authenticated encryption
- HSTS header – forces browsers to use HTTPS, preventing downgrade attacks
- Modern http2 directive – uses the separate
http2 on;directive (NGINX 1.25+)
Troubleshooting Virtual Hosts
Testing Configuration
Always test before reloading:
nginx -t
Checking Which Server Block Handles a Request
Use curl with a custom Host header:
curl -H "Host: example.com" http://localhost/
Common Issues
403 Forbidden errors: Usually caused by incorrect permissions or SELinux context. Check:
ls -laZ /var/www/example.com/
namei -om /var/www/example.com/html/index.html
Configuration not applied: NGINX reload might not pick up all changes. Try a restart:
systemctl restart nginx
Wrong server block responding: Check for duplicate default_server directives or overlapping server_name patterns.
If you encounter gateway errors while using virtual hosts with proxied backends, see our guides on 502 Bad Gateway and 504 Gateway Timeout errors.
Performance Considerations
For optimal virtual host performance:
Use Exact Server Names When Possible
Exact names are stored in a hash table and matched in O(1) time. Wildcards require additional processing, and regex patterns are evaluated sequentially.
Optimize server_names_hash
For many virtual hosts, increase the hash bucket size:
http {
server_names_hash_bucket_size 128;
server_names_hash_max_size 4096;
# ...
}
Monitor Memory Usage
Each server block with regex patterns consumes additional memory. Monitor your server’s memory usage when hosting many domains.
Conclusion
Configuring NGINX virtual hosts enables efficient multi-domain hosting on a single server. By understanding server block configuration, the server_name directive matching order, and best practices for organization and security, you can build a robust web hosting infrastructure.
Key takeaways:
- Use the
sites-availableandsites-enabledpattern for easy management - Create a dedicated user for each website for security isolation
- Always set correct SELinux contexts on Enterprise Linux
- Configure a default_server to handle unmatched requests
- Disable version disclosure with
server_tokens off - Use HTTPS with modern TLS configuration for production
- Test configurations before applying changes
Whether you’re hosting a few personal projects or managing dozens of client websites, NGINX virtual hosts provide the flexibility and performance you need.
