How to Host Multiple Websites on One Server with Apache or Nginx
Route many domains through one server and IP with Apache or Nginx, then add DNS, HTTPS, fallbacks, troubleshooting, and testing steps.
Yes, one server and one IP address can host many websites. DNS sends each hostname to the server. Apache selects a <VirtualHost> or NGINX selects a server block by the request hostname, then serves that site’s files or proxies to its application.
The reliable setup sequence is:
- Create a separate content directory or upstream target for every site.
- Point every hostname’s DNS record to the server’s public address.
- Create one Apache virtual host or NGINX server block per hostname.
- Choose an explicit fallback for unknown hostnames.
- Validate the configuration and reload the service.
- Test each hostname over HTTP, then configure certificates and HTTPS.
How name-based hosting works
A browser sends a request containing a hostname such as example.com. DNS first resolves that name to an IP address. After the connection reaches your server, the web server uses the hostname and destination port to select the matching site configuration. Several names can therefore share one IPv4 or IPv6 address while keeping separate roots, logs, redirects, and application upstreams.
Apache’s documentation recommends name-based hosting for ordinary deployments because it normally requires DNS mapping plus server configuration. See the Apache name-based virtual host documentation.
Prerequisites and planning checklist
- A running Linux server or other supported host with Apache HTTP Server or NGINX installed.
- Administrative access to edit configuration and reload the web server.
- One or more registered domain names and access to their DNS zones.
- Public network access to the ports you intend to use, normally 80 and 443.
- A content directory for each static site, or an application listening on a local address for proxying.
- File permissions that let the web-server worker read static files without making private files public.
- A certificate plan for every HTTPS hostname.
Write down the routing plan before editing configuration:
| Hostname | Content or upstream | HTTP port | HTTPS certificate |
|---|---|---|---|
| example.com | /var/www/example.com |
80 | example.com and www.example.com |
| example.net | /var/www/example.net |
80 | example.net |
DNS: point every hostname to the server
Virtual-host configuration does not create DNS records. Add an A record for IPv4 and, when applicable, an AAAA record for IPv6. Point aliases such as www.example.com either to the same address or to a CNAME according to your DNS provider’s rules.
example.com. A 203.0.113.10
www.example.com. A 203.0.113.10
example.net. A 203.0.113.10
Use your DNS provider’s tools or dig to confirm that public resolvers return the expected address:
dig +short example.com A
dig +short example.com AAAA
Check both address families. If IPv6 points somewhere else, some visitors can reach a different server even when IPv4 is correct.
Apache: configure one VirtualHost per website
Apache uses a <VirtualHost> block for each address and port combination. Set ServerName explicitly and add alternate names with ServerAlias. Give each site its own DocumentRoot, logs, access rules, and TLS directives as appropriate.
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
<Directory /var/www/example.com>
Require all granted
AllowOverride None
</Directory>
ErrorLog /var/log/apache2/example.com-error.log
CustomLog /var/log/apache2/example.com-access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName example.net
DocumentRoot /var/www/example.net
<Directory /var/www/example.net>
Require all granted
AllowOverride None
</Directory>
ErrorLog /var/log/apache2/example.net-error.log
CustomLog /var/log/apache2/example.net-access.log combined
</VirtualHost>
This demonstrates routing shape. File locations, include conventions, enablement commands, permissions, logging paths, and TLS directives differ by distribution. Adapt the paths to your installation.
Apache matching and fallback behavior
Apache first narrows candidates by destination IP address and port, then compares ServerName and ServerAlias. If no name matches, the first listed virtual host for that address and port becomes the fallback. Omitting ServerName can produce surprising matches, so define it in every block.
For an intentional fallback, put a dedicated first vhost on the relevant listener:
<VirtualHost *:80>
ServerName invalid.example
DocumentRoot /var/www/empty
<Directory /var/www/empty>
Require all denied
</Directory>
</VirtualHost>
Whether you deny, return a small error page, or redirect unknown hosts is a policy choice. The important part is that it is deliberate.
Proxying an Apache site to an application
A virtual host can proxy to an application instead of serving files. The exact proxy modules and security rules depend on your application. Keep the hostname routing in the virtual host and make the upstream target explicit.
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
Validate and inspect Apache
apachectl configtest
apachectl -S
apachectl -S prints Apache’s parsed virtual-host map, including address/port groups and names. Use it when a host appears to be ignored or the wrong site is selected. Reload through your operating system’s supported service workflow after validation.
NGINX: configure one server block per website
NGINX defines virtual servers with server directives inside the http context. Each normally has a listen directive and one or more server_name values.
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
server {
listen 80;
server_name example.net;
root /var/www/example.net;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
This is an illustrative shape. Adapt root directories, index files, access rules, logging, upstream settings, TLS, and distribution-specific include paths.
NGINX matching and default behavior
NGINX checks exact names first, then wildcard names, then regular expressions. Exact names are preferable where possible. Regular expressions are evaluated sequentially and can be slower or harder to audit.
If no configured name matches, NGINX uses the default server for that port. Without an explicit default_server, this is normally the first server listed for the port. Mark the intended fallback explicitly:
server {
listen 80 default_server;
server_name _;
return 444;
}
Choose a response suitable for your environment. A deny response, a controlled error page, or a redirect can all be valid; do not accidentally expose one customer’s site to unknown hostnames.
Proxying an NGINX site to an application
server {
listen 80;
server_name app.example.com;
location / {
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_pass http://127.0.0.1:3000;
}
}
Validate and reload NGINX
nginx -t
nginx -T
nginx -t checks syntax. nginx -T prints the complete loaded configuration, which helps confirm that the intended file is included and that another block is not taking precedence. Reload only after validation using your platform’s service workflow.
Apache and NGINX compared
| Decision | Apache HTTP Server | NGINX |
|---|---|---|
| Per-site unit | <VirtualHost> |
server block inside http |
| Hostname directive | ServerName and optional ServerAlias |
server_name |
| Listener | Address and port in <VirtualHost> |
listen |
| Unknown-host fallback | First matching vhost for address and port | First server for the port unless default_server is set |
| Name matching | Address/port candidates, then names | Exact, wildcard, then regular-expression names |
| Useful inspection | apachectl -S |
nginx -T plus documented matching rules |
Neither configuration pattern is universally faster or easier. Choose based on your existing modules, application integration, operational experience, and distribution conventions.
HTTPS and certificates for every hostname
HTTPS has its own hostname-selection step. During the TLS handshake, SNI lets Apache or NGINX choose the name-specific TLS configuration and certificate. Each certificate must cover every hostname it serves, including aliases such as www.
A common pattern is:
- Serve HTTP on port 80 for each hostname.
- Obtain certificates and configure port 443 blocks with the matching names.
- Redirect normal HTTP requests to HTTPS after certificate validation is working.
- Keep port 80 reachable if you use HTTP-01 renewal.
Let’s Encrypt HTTP-01 retrieves a challenge file over HTTP and requires inbound port 80. DNS-01 proves control with a TXT record at _acme-challenge, supports wildcard certificates, and does not require an inbound web connection. DNS API credentials used for automation should be protected. See the Let’s Encrypt challenge documentation and Certbot instructions.
Testing each site before publishing
- Verify DNS answers with
digfrom outside the server network. - Verify the expected listener is reachable:
curl -I http://example.com. - Check the returned status,
Location, and any site-identifying headers. - Test every alias, not only the primary domain.
- Test an unknown hostname and confirm the deliberate fallback.
- After TLS setup, run
curl -Iv https://example.comand confirm the certificate names. - Review access and error logs for the request you just made.
For a server-side test before DNS changes propagate, send the intended hostname while connecting to the server address:
curl --resolve example.com:80:203.0.113.10 http://example.com/
curl --resolve example.com:443:203.0.113.10 https://example.com/
Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Both domains show one site’s files | DNS points both names elsewhere, names are misspelled, or only one block loaded | Check DNS, compare the request hostname with ServerName/server_name, and inspect parsed configuration. |
| Unknown domain shows a real site | Apache’s first vhost or NGINX’s default server is serving it | Create and test an explicit fallback. |
| HTTPS shows the wrong certificate | Certificate lacks the hostname or the TLS name-specific block is not selected | Check SNI-capable configuration and certificate coverage for every name. |
| Certificate HTTP-01 validation fails | Port 80 is blocked, redirected incorrectly, or the challenge path reaches another site | Make port 80 externally reachable and route the challenge to the correct site. |
| DNS-01 validation fails | TXT record is missing, stale, or not yet propagated | Query the authoritative/public DNS response and wait for propagation before retrying. |
| Apache appears to ignore a host | File is not included, name or port differs, or another vhost wins | Run apachectl -S and compare the parsed map with the request. |
| NGINX will not start with a server-name hash error | Many or unusually long names exceed the configured hash construction limits | Only after seeing this error, review server_names_hash_max_size and server_names_hash_bucket_size, then recheck names. |
| Static files return 403 | Worker cannot traverse a parent directory or read the file | Correct ownership and directory execute/read permissions without making private directories public. |
| Application returns redirects to localhost | The upstream does not trust forwarded host or scheme headers | Pass the host and forwarded-protocol headers and configure the application to trust them. |
Performance, reliability, and cost considerations
- Capacity: There is no universal number of sites one server can support. File size, request rate, dynamic work, memory, TLS connections, databases, and background jobs determine capacity.
- Isolation: Separate Unix users, directories, logs, process limits, and application services reduce accidental cross-site access.
- Failure domain: One machine is one failure domain. A kernel, disk, network, or configuration failure can affect every hostname.
- Configuration safety: Validate before reload, keep configurations in version control, and retain a tested rollback.
- Logging: Use per-site access and error logs so routing mistakes and noisy applications can be diagnosed.
- Caching: Add caching only when responses are safe to share. Include the hostname in cache keys where your cache requires it.
- Cost: Consolidating sites can reduce the number of servers you operate, but the correct size depends on workload and availability requirements. The source material provides no universal savings or performance benchmark.
Or skip the browser setup
If your goal is to capture each hosted site as an image or PDF, ScreenshotNeo accepts one GET request and returns a PNG, JPEG, WebP, or PDF. It removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for the complete option list.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
There are 63 options, including full-page lazy-image loading, CSS-element capture, dark mode, device presets, arbitrary viewports, retina scale, PDF paper and page ranges, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, and a usage API. The parameter names used by other screenshot APIs also work, which can simplify migration.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Can every site use the same IP address?
Yes, for name-based HTTP and HTTPS hosting, provided DNS points the names to that address and the server has matching configurations. Resource limits and isolation still determine whether one machine is a good operational choice.
Do I need a separate IP for each certificate?
Normally no. SNI allows the TLS handshake to select a hostname-specific configuration and certificate on a shared address.
Should I use wildcard hostnames?
Use exact names when the set of sites is known. Wildcards can simplify patterns, but they make fallback and ownership rules harder to audit.
What happens when someone sends no or an unknown Host header?
Apache uses the first matching vhost for the address and port. NGINX uses the port’s default server. Configure those fallbacks intentionally so an unrelated site’s content is not exposed.
Can a site serve files while another proxies to an app?
Yes. Each Apache virtual host or NGINX server block can independently use a document root, a proxy upstream, or another supported handler.
Why does a DNS change appear inconsistent?
Resolvers cache records for their TTL, and IPv4 and IPv6 may follow different paths. Query both record types and test from an external network while propagation completes.


