How to Set Up Virtual Hosts (Nginx or Apache) on Fedora

To set up virtual hosts on Fedora, create a dedicated configuration file in `/etc/nginx/conf.d/` or `/etc/httpd/conf.d/`, define the `server_name` or `ServerName` directive with your domain, and reload the web server service.

You see the default page, not your site

You pointed your domain to the Fedora server. You installed the web server. You created a configuration file. You visit the site and see the "Welcome to nginx!" page or the Apache test page. Or you get a 403 Forbidden error. You feel like you did everything right, but the server is ignoring your rules.

The issue is usually a mismatch in the routing logic, a missing directory, a permission error, or SELinux blocking the request. Fedora ships with strict defaults to keep the system secure. The web server matches requests to configurations based on the Host header. If your configuration doesn't match exactly, or if the directory structure is wrong, the server falls back to the default block. Or SELinux denies access silently. This article walks through the exact steps to define a virtual host, handle permissions, and verify the routing works.

What's actually happening

A virtual host is a mapping between a domain name and a directory on disk. The web server listens on an IP address and port. When a request arrives, the server reads the Host header sent by the browser. It scans all loaded configuration blocks for a server_name or ServerName that matches that header. The first match wins. If no match is found, the server uses the default block. This mechanism allows one server to host dozens of sites on a single IP address.

The configuration files live in /etc/nginx/conf.d/ or /etc/httpd/conf.d/. The main configuration includes everything in these directories automatically. You never edit the main config to add a site. You create a new file in the conf.d directory. This keeps your changes separate from package updates. Files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/.

Think of the web server as a building with a single entrance. The virtual host configuration is the directory inside. When a visitor arrives, the receptionist checks the name on their invitation (the Host header). If the name matches a tenant, the receptionist directs them to the correct office. If the name is unknown, the receptionist sends them to the lobby. Your job is to register the tenant and ensure the office door is unlocked.

Nginx virtual host setup

Nginx uses a server block to define a virtual host. The listen directive binds the port. The server_name handles routing. The root directive sets the base path. The location block handles the request logic.

Here's how to create the configuration file for Nginx. The tee command writes the content safely with root privileges.

sudo tee /etc/nginx/conf.d/example.com.conf > /dev/null <<EOF
server {
    listen 80;
    # Binds this server block to port 80 for incoming HTTP requests.
    # If you add 'default_server' here, this becomes the fallback for unmatched hosts.

    server_name example.com www.example.com;
    # Matches the Host header in the request.
    # Nginx selects the first server block where this list contains the requested host.

    root /var/www/example.com/html;
    # Sets the base directory for file lookups.
    # A request for /index.html maps to /var/www/example.com/html/index.html.

    index index.html;
    # Defines the default file to serve when a directory is requested.

    location / {
        try_files $uri $uri/ =404;
        # Checks if the requested file exists.
        # If not, checks if a directory exists and serves the index file.
        # If neither exists, returns a 404 error.
        # This prevents Nginx from serving the index file for every missing path.
    }
}
EOF

Create the document root and set permissions. Nginx worker processes run as the nginx user on Fedora. The directory must be readable by that user.

sudo mkdir -p /var/www/example.com/html
# Create the directory structure for the site.
# The -p flag creates parent directories if they don't exist.

sudo touch /var/www/example.com/html/index.html
# Create a placeholder file so the directory isn't empty.
# An empty directory can cause unexpected behavior with some configurations.

sudo chown -R nginx:nginx /var/www/example.com
# Set ownership to the nginx user and group.
# Nginx needs read access to serve the files.
# On Fedora, the Nginx worker runs as user 'nginx', not 'www-data'.

Test the syntax before reloading. A typo in the configuration can prevent Nginx from starting.

sudo nginx -t
# Checks configuration syntax and reports errors without restarting.
# Look for 'syntax is ok' and 'test is successful' in the output.

sudo systemctl reload nginx
# Gracefully reloads the configuration.
# Reload applies changes without dropping active connections.
# Use restart only if reload fails or you changed the listen port.

Create the directory before the config. An empty config pointing to a missing path causes a 404.

Apache virtual host setup

Apache uses a <VirtualHost> block. The ServerName and ServerAlias directives handle routing. The DocumentRoot sets the base path. The <Directory> block controls access. Apache defaults to denying access to everything. You must explicitly grant access.

Here's how to create the configuration file for Apache.

sudo tee /etc/httpd/conf.d/example.com.conf > /dev/null <<EOF
<VirtualHost *:80>
    ServerName example.com
    # Primary domain for this virtual host.
    # Apache matches this against the Host header.

    ServerAlias www.example.com
    # Additional domains that should be treated as this site.
    # Requests for www.example.com will be routed here.

    DocumentRoot /var/www/example.com/html
    # Directory to serve files from.
    # Apache maps the URL path to this directory.

    <Directory /var/www/example.com/html>
        Require all granted
        # Allows access to this directory.
        # Fedora's Apache defaults deny access to all directories.
        # This directive replaces the old 'Allow from all' syntax.
    </Directory>
</VirtualHost>
EOF

Create the directory and set permissions. Apache runs as the apache user on Fedora.

sudo mkdir -p /var/www/example.com/html
# Create the document root directory.

sudo touch /var/www/example.com/html/index.html
# Create a placeholder index file.

sudo chown -R apache:apache /var/www/example.com
# Set ownership to the apache user and group.
# Apache needs read access to serve the files.
# On Fedora, Apache runs as user 'apache', not 'www-data'.

Test the syntax and reload. Apache's configtest command validates the configuration.

sudo apachectl configtest
# Checks Apache configuration syntax.
# Look for 'Syntax OK' in the output.
# If there is an error, the output shows the file and line number.

sudo systemctl reload httpd
# Reloads Apache configuration gracefully.
# Reload restarts worker processes without stopping the service.
# This minimizes downtime for existing connections.

Test the syntax before reloading. A typo in the config can crash the service.

Verify it worked

DNS propagation can take hours. You can test the virtual host immediately by using curl with the Host header. This simulates a browser request for the specific domain.

curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://localhost
# Sends a request to localhost with the Host header set to example.com.
# The -s flag silences progress. -o /dev/null discards the body.
# -w prints the HTTP status code.
# A result of 200 means the virtual host is working.

If you see 200, the server is routing correctly. If you see 403, check permissions and SELinux. If you see 404, check the root or DocumentRoot path. If you see the default page, the server_name or ServerName doesn't match.

You can also add a temporary entry to /etc/hosts to test with a browser. This maps the domain to the local IP address.

echo "127.0.0.1 example.com" | sudo tee -a /etc/hosts
# Adds a local DNS entry for testing.
# This overrides external DNS for this domain on this machine.
# Remove this line after testing to avoid confusion.

Check the response code. If you get 403, check permissions and SELinux. If you get 404, check the root path.

Common pitfalls and errors

SELinux is the most common cause of silent failures on Fedora. SELinux labels files with contexts. The web server process has a label. It can only access files with specific labels. /var/www is labeled httpd_sys_content_t. If you put files elsewhere, or if the context gets messed up, the server gets a permission denied error. The error log shows Permission denied, but the real cause is SELinux.

Here's how to check for SELinux denials.

sudo ausearch -m avc -ts recent
# Shows SELinux access vector cache denials from recent events.
# Look for lines mentioning 'nginx' or 'httpd' and 'denied'.
# The output shows the source process and the target file context.

If you see denials, fix the context. Use semanage to define the correct context and restorecon to apply it.

sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
# Updates the policy database with the new file context rule.
# The regex matches the directory and all files inside it.
# This command does not change files on disk. It updates the policy.

sudo restorecon -Rv /var/www/example.com
# Resets file labels based on the policy.
# The -R flag applies recursively. The -v flag shows changes.
# This command applies the context to existing files immediately.

Firewall rules can also block access. Fedora uses firewalld. Ensure HTTP traffic is allowed.

sudo firewall-cmd --permanent --add-service=http
# Adds the HTTP service to the permanent firewall configuration.
# The http service includes port 80/tcp.
# Use --add-service=https for port 443.

sudo firewall-cmd --reload
# Applies the persistent config to the runtime.
# Always run this after changes.
# The runtime and persistent configs diverge if you skip this.

Run firewall-cmd --reload after every rule change. The runtime and persistent configs diverge otherwise.

Check journalctl -xeu nginx for the real error. The browser only shows the result.

When to use Nginx vs Apache

Use Nginx when you need high concurrency and static file performance with low memory usage. Use Nginx when you are reverse-proxying to a backend application like Node.js, Python, or Go. Use Apache when you rely on .htaccess files for per-directory configuration. Use Apache when you are deploying a legacy application that expects mod_php or specific Apache modules. Use Apache when you need complex mod_rewrite rules that are difficult to replicate in Nginx. Stick with Nginx if you are hosting static sites or simple APIs. Stick with Apache if you are migrating an existing LAMP stack and want minimal configuration changes.

Use Nginx when you need high concurrency and static file performance with low memory usage. Use Apache when you rely on .htaccess files or complex mod_rewrite rules. Use Apache when you are deploying a legacy application that expects mod_php or specific Apache modules. Stick with Nginx if you are reverse-proxying to a backend application like Node.js or Python.

Where to go next