Create NGINX virtual hosts
This article shows you how to create NGINX® virtual hosts that serve
multiple web domains.
Introduction
Virtual hosts (also called server blocks in NGINX terminology) enable you to host multiple websites or applications on a single server. Each virtual host can serve different domains, respond to different ports, or handle traffic based on various conditions. This guide covers contemporary best practices for configuring NGINX virtual hosts on modern Linux systems.
Prerequisites
- A Linux server with NGINX installed (Ubuntu 22.04/24.04, Debian 11/12, RHEL 9, Rocky Linux 9, or similar).
- Root or sudo access.
- Basic understanding of DNS configuration.
- Domain names pointing to your server's IP address.
Understanding NGINX Directory Structure
Modern NGINX installations typically use this structure:
/etc/nginx/nginx.conf - Main configuration file
/etc/nginx/sites-available/ - Individual site configurations (Ubuntu/Debian)
/etc/nginx/sites-enabled/ - Active site configurations (Ubuntu/Debian)
/etc/nginx/conf.d/ - Alternative for site configs (RHEL/Rocky/Alma/EL)
/var/www/ - Default web root directory
/var/log/nginx - Log files location
Note: RHEL-based systems typically use /etc/nginx/conf.d/ exclusively, while Debian-based systems use the sites-available/sites-enabled pattern.
Prepare Your Directory Structure
Create directories for each website:
$ sudo mkdir -p /var/www/example.com/public_html
$ sudo mkdir -p /var/www/example.com/logs
$ sudo mkdir -p /var/www/testsite.net/public_html
$ sudo mkdir -p /var/www/testsite.net/logsSet appropriate ownership:
$ sudo chown -R $USER:$USER /var/www/example.com
$ sudo chown -R $USER:$USER /var/www/testsite.netSet secure permissions:
$ sudo chmod -R 755 /var/wwwCreate Sample Content
Create a test page for the first site:
# cat > /var/www/example.com/public_html/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to Example.com</title>
</head>
<body>
<h1>Success! Example.com is working</h1>
<p>This virtual host is configured correctly.</p>
</body>
</html>
EOFCreate a test page for the second site:
# cat > /var/www/testsite.net/public_html/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to TestSite.net</title>
</head>
<body>
<h1>Success! TestSite.net is working</h1>
<p>This virtual host is configured correctly.</p>
</body>
</html>
EOFCreate Virtual Host Configuration Files
For Ubuntu/Debian Systems
Create the first virtual host configuration:
$ sudo vim /etc/nginx/sites-available/example.comAdd this configuration:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.html index.htm index.php;
access_log /var/www/example.com/logs/access.log;
error_log /var/www/example.com/logs/error.log;
location / {
try_files $uri $uri/ =404;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Disable access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Create the second virtual host:
$ sudo vim /etc/nginx/sites-available/testsite.netserver {
listen 80;
listen [::]:80;
server_name testsite.net www.testsite.net;
root /var/www/testsite.net/public_html;
index index.html index.htm index.php;
access_log /var/www/testsite.net/logs/access.log;
error_log /var/www/testsite.net/logs/error.log;
location / {
try_files $uri $uri/ =404;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Disable access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
Enable the sites by creating symbolic links:
$ sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
$ sudo ln -s /etc/nginx/sites-available/testsite.net /etc/nginx/sites-enabled/For RHEL/Rocky/AlmaLinux Systems
You can create the file in "/etc/nginx/conf.d/" or create a "/etc/nginx/vhosts/" as well.
However, you will need to mention it in the "/etc/nginx/nginx.conf" file to include the directory structure.
$ grep vhosts /etc/nginx/nginx.conf
include /etc/nginx/vhosts/*.conf;Create configuration files directly in /etc/nginx/conf.d/
$ sudo vim /etc/nginx/conf.d/example.com.confUse the same server block configuration as shown above. Repeat for additional sites with different filenames (e.g., testsite.net.conf).
Test and Apply Configuration
Always test your configuration before applying:
$ sudo nginx -tYou should see:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If tests pass, reload NGINX and check status:
$ sudo systemctl reload nginx
$ sudo systemctl status nginxConfigure Firewall
If you are using a firewall at the OS level, the below commands will be helpful.
Allow HTTP and HTTPS traffic:
For UFW (Ubuntu/Debian):
$ sudo ufw allow 'Nginx Full'For firewalld (RHEL/Rocky):
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reloadOther Configuration Examples:
Virtual Host with PHP Support:
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Virtual Host with SSL/TLS (HTTPS)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/public_html;
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
try_files $uri $uri/ =404;
}
}
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
Reverse Proxy Configuration
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Troubleshooting Common Issues
You can review the "basic-nginx-troubleshooting" article as well.
Check NGINX Error Logs:
$ sudo tail -f /var/log/nginx/error.log Verify Port Listening:
$ sudo ss -tlnp | grep nginxTest Configuration Syntax:
$ sudo nginx -tCheck SELinux Status (RHEL-based):
If you're having permission issues on RHEL systems:
$ sestatus ### (if enforced you can check below)
$ sudo setsebool -P httpd_can_network_connect 1
$ sudo chcon -R -t httpd_sys_content_t /var/www/Common Error: "Address Already in Use."
This means another service is using port 80 or 443:
$ sudo systemctl status nginx
× nginx.service - The nginx HTTP and reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/nginx.service.d
└─php-fpm.conf
Active: failed (Result: exit-code) since Fri 2026-01-09 10:38:06 UTC; 5s ago
Process: 110059 ExecStartPre=/usr/bin/rm -f /run/nginx.pid (code=exited, status=0/SUCCESS)
Process: 110060 ExecStartPre=/usr/sbin/nginx -t (code=exited, status=0/SUCCESS)
Process: 110061 ExecStart=/usr/sbin/nginx (code=exited, status=1/FAILURE)
CPU: 34ms
Jan 09 10:38:04 testing-el nginx[110061]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Jan 09 10:38:04 testing-el nginx[110061]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
Jan 09 10:38:05 testing-el nginx[110061]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Jan 09 10:38:05 testing-el nginx[110061]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
Jan 09 10:38:05 testing-el nginx[110061]: nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
Jan 09 10:38:05 testing-el nginx[110061]: nginx: [emerg] bind() to [::]:80 failed (98: Address already in use)
Jan 09 10:38:06 testing-el nginx[110061]: nginx: [emerg] still could not bind()
Jan 09 10:38:06 testing-el systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
Jan 09 10:38:06 testing-el systemd[1]: nginx.service: Failed with result 'exit-code'.
Jan 09 10:38:06 testing-el systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
$ sudo lsof -i :80
$ sudo lsof -i :443Note: In the scenario described above, you have two options: you can either change the port for nginx in its configuration or, if another service like Apache or HTTP is not in use, you can stop that service to use nginx instead.
After stopping the other service, remember to restart or reload nginx.
Additional
Adding these directives to your virtual host can increase your performance:
# Enable gzip compression
gzip on;
gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml text/javascript;
# Browser caching for static files
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
Most Linux distributions include a default NGINX log rotation configuration, but creating custom configurations per virtual host provides better control and organization.
Best Practices
- Use separate configuration files for each virtual host rather than one large file.
- Always test the configuration with nginx -t before reloading.
- Keep logs organized by storing each site's logs in its own directory.
- Implement security headers to protect against common vulnerabilities.
- Use SSL/TLS for all production websites (Let's Encrypt provides free certificates).
- Set appropriate file permissions (755 for directories, 644 for files).
- Regular backups of configuration files before making changes
- Monitor logs regularly for errors and suspicious activity.
- Keep NGINX updated to the latest stable version.
- Document your configurations with comments for future reference.
Updated about 14 hours ago
