Web Server Configuration

Web Server Configuration

Introduction

Setting up a web server on MidnightBSD allows you to host websites, web applications, and APIs. MidnightBSD supports both nginx and Apache HTTP Server through mports, giving you flexibility to choose the web server that best fits your needs. Both servers are production-ready and widely used in enterprise environments.

This guide covers installation, configuration, security hardening, and performance tuning for both nginx and Apache on MidnightBSD, including setting up HTTPS with Let's Encrypt certificates and configuring virtual hosts for multiple websites.

Choosing Between Nginx and Apache

Use Nginx if you need:

Use Apache if you need:

Common approach: Many deployments use both - nginx as a reverse proxy in front of Apache for static content and SSL termination, with Apache handling dynamic content. This provides the performance benefits of nginx with the flexibility of Apache.

Nginx

Installation

Nginx is available through mports and can be installed with:

# mport install nginx

Enable nginx to start at boot:

# sysrc nginx_enable=YES
# service nginx start

Verify nginx is running:

# service nginx status
# ps aux | grep nginx

Test nginx installation: After starting nginx, visit http://localhost in your browser. You should see the default nginx welcome page.

Nginx files and directories:

Basic Configuration

The main nginx configuration file is /usr/local/etc/nginx/nginx.conf. It includes other configuration files from the conf.d/ directory.

Main configuration structure:

# Main context - affects all server blocks
user  www;
worker_processes  auto;

# Error log location
error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

# Events context
events {
    worker_connections  1024;
}

# HTTP context - contains server blocks
http {
    include       /usr/local/etc/nginx/mime.types;
    default_type  application/octet-stream;

    # Log format definitions
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    # Basic settings
    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout  65;
    types_hash_max_size 2048;

    # Server configuration
    include /usr/local/etc/nginx/conf.d/*.conf;
}

Testing configuration: After making changes to nginx configuration:

# Test configuration syntax
nginx -t

# If test passes, reload nginx
service nginx reload

Basic server block:

server {
    listen       80;
    server_name  localhost;
    
    # Root directory
    root   /usr/local/www/nginx;
    index  index.html index.htm;

    # Location blocks
    location / {
        try_files $uri $uri/ =404;
    }

    # Error pages
    error_page  404  /404.html;
    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/local/www/nginx;
    }
}

Common configuration directives:

Directive Description Example
listen IP address and port to listen on listen 80; or listen 192.168.1.100:80;
server_name Server names (domain names) for this block server_name example.com www.example.com;
root Root directory for requests root /usr/local/www/example;
index Default files to serve index index.html index.php;
location Defines how to handle requests to specific paths location /images/ { ... }
try_files Try files in order until one is found try_files $uri $uri/ =404;

Virtual Hosts

Virtual hosts allow you to host multiple websites on a single server. Create separate configuration files for each site in /usr/local/etc/nginx/conf.d/.

Basic virtual host setup:

# Create configuration file for example.com
vi /usr/local/etc/nginx/conf.d/example.com.conf

server {
    listen       80;
    server_name  example.com www.example.com;
    
    root         /usr/local/www/example.com;
    index        index.html index.php;

    # Custom error pages
    error_page  404  /404.html;

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

    # PHP-FPM configuration (if using PHP)
    location ~ \.php$ {
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }
}

# Reload nginx to apply changes
nginx -t && service nginx reload

IP-based virtual hosts:

# Multiple IPs on same server
server {
    listen       192.168.1.100:80;
    server_name  site1.com;
    root         /usr/local/www/site1;
}

server {
    listen       192.168.1.101:80;
    server_name  site2.com;
    root         /usr/local/www/site2;
}

Port-based virtual hosts:

# Same IP, different ports
server {
    listen       80;
    server_name  example.com;
    root         /usr/local/www/example-http;
}

server {
    listen       8080;
    server_name  example.com;
    root         /usr/local/www/example-admin;
}

Default server block:

# Default server - catches all requests not matching other server_name directives
server {
    listen       80 default_server;
    server_name  _;
    root         /usr/local/www/default;
    
    # Return 444 to drop connection for undefined server names
    # return 444;
}

HTTPS with Let's Encrypt

Setting up HTTPS with free certificates from Let's Encrypt using Certbot.

Install Certbot and nginx plugin:

# mport install py312-certbot py312-certbot-nginx

Obtain certificate:

# Stop nginx temporarily
service nginx stop

# Obtain certificate (webroot method)
certbot certonly --webroot -w /usr/local/www/example.com -d example.com -d www.example.com

# Or standalone method (nginx must be stopped)
certbot certonly --standalone -d example.com -d www.example.com

# Start nginx again
service nginx start

Automatic certificate renewal:

# Test renewal
certbot renew --dry-run

# Add to cron for automatic renewal (runs twice daily)
0 */12 * * * /usr/local/bin/certbot renew --quiet --post-hook "service nginx reload"

Configure nginx for HTTPS:

server {
    listen       80;
    server_name  example.com www.example.com;
    return       301 https://$host$request_uri;
}

server {
    listen       443 ssl;
    http2        on;
    server_name  example.com www.example.com;
    
    # SSL certificate configuration
    ssl_certificate      /usr/local/etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key  /usr/local/etc/letsencrypt/live/example.com/privkey.pem;
    
    # SSL protocols and ciphers
    ssl_protocols        TLSv1.2 TLSv1.3;
    ssl_ciphers          HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Strong SSL settings
    ssl_session_cache    shared:SSL:10m;
    ssl_session_timeout  10m;
    ssl_session_tickets off;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # Root directory
    root   /usr/local/www/example.com;
    index  index.html index.php;

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

Security headers:

# Add to your HTTPS server block
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

HTTP/2 support: Enable HTTP/2 in the HTTPS server block with listen 443 ssl; followed by http2 on;.

Performance Tuning

Worker processes:

# In nginx.conf
events {
    worker_connections  2048;  # Maximum connections per worker
}

http {
    # Auto-detect number of CPU cores
    worker_processes  auto;
}

Buffer and timeout settings:

http {
    # Buffer sizes
    client_body_buffer_size     16k;
    client_header_buffer_size   1k;
    client_max_body_size        10m;
    large_client_header_buffers 4 16k;

    # Timeouts
    client_body_timeout   12;
    client_header_timeout 12;
    keepalive_timeout    75;
    send_timeout         12;
}

Gzip compression:

http {
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1000;
}

File serving optimization:

http {
    # Enable sendfile for static files
    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;

    # Cache static files
    open_file_cache          max=1000 inactive=20s;
    open_file_cache_valid    30s;
    open_file_cache_min_uses 2;
    open_file_cache_errors   on;
}

# For static file locations
location /static/ {
    expires 30d;
    add_header Cache-Control "public, no-transform";
}

Reverse Proxy Setup

Configure nginx as a reverse proxy to forward requests to backend servers (Apache, Node.js, etc.).

Reverse proxy to application server:

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;
    }
}

Reverse proxy to Apache:

upstream apache_backend {
    server 127.0.0.1:8080;
}

server {
    listen       80;
    server_name  example.com;

    # Static files served by nginx
    location / {
        root /usr/local/www/example.com;
        try_files $uri @apache;
    }

    # Dynamic content proxied to Apache
    location @apache {
        proxy_pass http://apache_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Static files cache
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }
}

Load balancing across multiple backend servers:

upstream backend_servers {
    server 192.168.1.101:8000;
    server 192.168.1.102:8000;
    server 192.168.1.103:8000;
    
    # Load balancing algorithm
    least_conn;  # Send request to server with least active connections
    # Other options: round_robin (default), ip_hash, least_time
}

server {
    listen       80;
    server_name  app.example.com;

    location / {
        proxy_pass http://backend_servers;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Common Nginx Tasks

Create a new website:

# 1. Create directory structure
mkdir -p /usr/local/www/mysite.com/{public_html,logs,cgi-bin}

# 2. Create basic index.html
cat > /usr/local/www/mysite.com/public_html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Welcome to mysite.com</title></head>
<body><h1>mysite.com</h1></body>
</html>
EOF

# 3. Create nginx configuration
cat > /usr/local/etc/nginx/conf.d/mysite.com.conf << 'EOF'
server {
    listen       80;
    server_name  mysite.com www.mysite.com;
    root         /usr/local/www/mysite.com/public_html;
    index        index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
EOF

# 4. Test and reload
nginx -t && service nginx reload

Redirect HTTP to HTTPS:

server {
    listen       80;
    server_name  example.com www.example.com;
    return       301 https://$host$request_uri;
}

Redirect www to non-www:

server {
    listen       80;
    server_name  www.example.com;
    return       301 https://example.com$request_uri;
}

Serve static files efficiently:

location /static/ {
    root /usr/local/www;
    expires 30d;
    add_header Cache-Control "public, no-transform";
    
    # Disable access logging for static files
    access_log off;
}

Disable directory listing:

location / {
    autoindex off;
}

Password protect a directory:

# 1. Create password file
htpasswd -c /usr/local/etc/nginx/.htpasswd admin
# You'll be prompted for the password

# 2. Configure location
location /admin/ {
    auth_basic "Admin Area";
    auth_basic_user_file /usr/local/etc/nginx/.htpasswd;
}

Enable HTTP/2:

listen 443 ssl;
http2 on;

Apache HTTP Server

Installation

Apache HTTP Server (httpd) is available through mports:

# mport install apache24

Enable Apache to start at boot:

# sysrc apache24_enable=YES
# service apache24 start

Verify Apache is running:

# service apache24 status
# ps aux | grep httpd

Test Apache installation: Visit http://localhost in your browser. You should see the Apache default page.

Apache files and directories:

Basic Configuration

The main Apache configuration file is /usr/local/etc/apache24/httpd.conf. It includes several other configuration files.

Key configuration sections:

# Global Environment
ServerRoot "/usr/local"

# Main server configuration
Listen 80
ServerAdmin admin@example.com
ServerName localhost:80

# DocumentRoot: The directory out of which you will serve your
# documents. By default, all requests are taken from this directory
DocumentRoot "/usr/local/www/apache24/data"

# Directory configuration
<Directory "/usr/local/www/apache24/data">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

# Include additional configuration files
Include /usr/local/etc/apache24/extra/*.conf

Testing configuration:

# Test configuration syntax
apachectl -t

# If test passes, restart Apache
service apache24 restart

Configuration file hierarchy:

Common Apache directives:

Directive Description Example
Listen IP address and port to listen on Listen 80 or Listen 192.168.1.100:80
ServerName Server name and port ServerName example.com:80
DocumentRoot Root directory for documents DocumentRoot /usr/local/www/example
DirectoryIndex Default files to serve DirectoryIndex index.html index.php
ErrorLog Error log location ErrorLog /var/log/apache24/error.log
CustomLog Access log location and format CustomLog /var/log/apache24/access.log common

Virtual Hosts

Apache virtual hosts can be configured in /usr/local/etc/apache24/extra/httpd-vhosts.conf or in individual files in the Includes/ directory.

Name-based virtual hosts:

# In /usr/local/etc/apache24/extra/httpd-vhosts.conf

<VirtualHost *:80>
    ServerAdmin admin@example.com
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /usr/local/www/example.com
    
    <Directory /usr/local/www/example.com>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog /var/log/apache24/example.com-error.log
    CustomLog /var/log/apache24/example.com-access.log common
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin admin@anothersite.com
    ServerName anothersite.com
    ServerAlias www.anothersite.com
    DocumentRoot /usr/local/www/anothersite.com
    
    <Directory /usr/local/www/anothersite.com>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog /var/log/apache24/anothersite.com-error.log
    CustomLog /var/log/apache24/anothersite.com-access.log common
</VirtualHost>

IP-based virtual hosts:

<VirtualHost 192.168.1.100:80>
    ServerName site1.com
    DocumentRoot /usr/local/www/site1
</VirtualHost>

<VirtualHost 192.168.1.101:80>
    ServerName site2.com
    DocumentRoot /usr/local/www/site2
</VirtualHost>

Port-based virtual hosts:

# Main site on port 80
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /usr/local/www/example-http
</VirtualHost>

# Admin site on port 8080
<VirtualHost *:8080>
    ServerName example.com
    DocumentRoot /usr/local/www/example-admin
</VirtualHost>

Default virtual host:

# First virtual host defined becomes the default
<VirtualHost *:80>
    ServerName default.example.com
    DocumentRoot /usr/local/www/default
    
    # Or redirect all undefined hosts
    Redirect permanent / https://example.com/
</VirtualHost>

HTTPS Configuration

Setting up HTTPS with Apache using Let's Encrypt certificates.

Enable SSL module:

# Check if SSL module is loaded (look for mod_ssl.so)
httpd -M | grep ssl

# If not enabled, uncomment or add to httpd.conf
LoadModule ssl_module modules/mod_ssl.so

# Include SSL configuration
Include /usr/local/etc/apache24/extra/httpd-ssl.conf

Obtain Let's Encrypt certificate:

# Install certbot
mport install py312-certbot py312-certbot-apache

# Stop Apache temporarily
service apache24 stop

# Obtain certificate
certbot certonly --standalone -d example.com -d www.example.com

# Start Apache again
service apache24 start

Configure HTTPS virtual host:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /usr/local/www/example.com
    
    # SSL Configuration
    SSLEngine on
    SSLCertificateFile /usr/local/etc/letsencrypt/live/example.com/fullchain.pem
    SSLCertificateKeyFile /usr/local/etc/letsencrypt/live/example.com/privkey.pem
    
    # SSL protocols and ciphers
    SSLProtocol -all +TLSv1.2 +TLSv1.3
    SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
    SSLHonorCipherOrder on
    
    # Security headers
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"

    <Directory /usr/local/www/example.com>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog /var/log/apache24/example.com-ssl-error.log
    CustomLog /var/log/apache24/example.com-ssl-access.log common
</VirtualHost>

Redirect HTTP to HTTPS:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    
    Redirect permanent / https://example.com/
</VirtualHost>

Automatic certificate renewal:

# Add to cron
0 */12 * * * /usr/local/bin/certbot renew --quiet --post-hook "service apache24 restart"

Performance Tuning

MPM (Multi-Processing Module) settings:

# Check current MPM
httpd -V | grep -i mpm

# For prefork MPM (traditional, one process per connection)
<IfModule mpm_prefork_module>
    StartServers          5
    MinSpareServers       5
    MaxSpareServers      10
    MaxRequestWorkers   256
    MaxConnectionsPerChild  1000
</IfModule>

# For event MPM (recommended for high traffic, similar to nginx)
<IfModule mpm_event_module>
    StartServers          3
    MinSpareThreads      75
    MaxSpareThreads     250
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers   400
    MaxConnectionsPerChild  1000
</IfModule>

# For worker MPM (hybrid approach)
<IfModule mpm_worker_module>
    StartServers          3
    MinSpareThreads      75
    MaxSpareThreads     250
    ThreadLimit          64
    ThreadsPerChild      25
    MaxRequestWorkers   400
    MaxConnectionsPerChild  1000
</IfModule>

KeepAlive settings:

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15

Memory and cache settings:

# File cache
CacheEnable disk /
CacheDefaultExpire 3600
CacheMaxFileSize 1000000
CacheRoot /var/cache/apache24

# Memory settings
RLimitMEM 512000 1024000  # Min and Max memory in bytes

Compression (mod_deflate):

# Enable deflate module (usually already loaded)
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript
    # Don't compress images
    SetOutputFilter DEFLATE
    SetEnv no-gzip 1
</IfModule>

Enable caching headers:

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/html "access plus 1 hour"
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType text/javascript "access plus 1 week"
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
</IfModule>

Common Apache Tasks

Create a new website:

# 1. Create directory structure
mkdir -p /usr/local/www/mysite.com/{public_html,logs,cgi-bin}

# 2. Create basic index.html
cat > /usr/local/www/mysite.com/public_html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head><title>Welcome to mysite.com</title></head>
<body><h1>mysite.com</h1></body>
</html>
EOF

# 3. Create virtual host configuration
cat > /usr/local/etc/apache24/Includes/mysite.com.conf << 'EOF'
<VirtualHost *:80>
    ServerName mysite.com
    ServerAlias www.mysite.com
    DocumentRoot /usr/local/www/mysite.com/public_html
    
    <Directory /usr/local/www/mysite.com/public_html>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog /var/log/apache24/mysite.com-error.log
    CustomLog /var/log/apache24/mysite.com-access.log common
</VirtualHost>
EOF

# 4. Test and restart Apache
apachectl -t && service apache24 restart

Enable .htaccess files:

# In your virtual host or directory configuration
<Directory /usr/local/www/mysite.com/public_html>
    Options Indexes FollowSymLinks
    AllowOverride All  # This enables .htaccess
    Require all granted
</Directory>

Password protect a directory:

# 1. Create password file
htpasswd -c /usr/local/etc/apache24/.htpasswd admin
# You'll be prompted for the password

# 2. Configure directory in virtual host
<Directory /usr/local/www/mysite.com/private>
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /usr/local/etc/apache24/.htpasswd
    Require valid-user
</Directory>

Enable CGI scripts:

# 1. Ensure CGI module is loaded (usually enabled by default)
# LoadModule cgid_module modules/mod_cgid.so

# 2. Configure CGI directory
<Directory /usr/local/www/mysite.com/cgi-bin>
    Options +ExecCGI
    AddHandler cgi-script .cgi .pl
    Require all granted
</Directory>

# 3. Create a test CGI script
cat > /usr/local/www/mysite.com/cgi-bin/test.cgi << 'EOF'
#!/bin/sh
echo "Content-type: text/html"
echo ""
echo "<html><body><h1>CGI Test Successful</h1></body></html>"
EOF
chmod +x /usr/local/www/mysite.com/cgi-bin/test.cgi

# 4. Test in browser: http://mysite.com/cgi-bin/test.cgi

Custom error pages:

# In your virtual host configuration
ErrorDocument 401 /errors/401.html
ErrorDocument 403 /errors/403.html
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.html

# Create error directory and pages
mkdir -p /usr/local/www/mysite.com/errors
# Create your custom error pages in this directory

Disable directory listing:

<Directory /usr/local/www/mysite.com/public_html>
    Options -Indexes
</Directory>

.htaccess Configuration

.htaccess files allow directory-level configuration without editing the main Apache configuration.

Enable .htaccess: Make sure AllowOverride All is set for the directory.

Common .htaccess examples:

Basic authentication:

AuthType Basic
AuthName "Restricted Access"
AuthUserFile /usr/local/www/.htpasswd
Require valid-user

Rewrite URLs:

RewriteEngine On
RewriteRule ^old-page.html$ /new-page.html [R=301,L]

# Remove .html extension
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^\.]+)$ $1.html [L]

Custom error documents:

ErrorDocument 404 /custom-404.html
ErrorDocument 403 /custom-403.html

Prevent hotlinking:

RewriteEngine On
RewriteCond %{HTTP_REFERER} !^$ [NC]
RewriteCond %{HTTP_REFERER} !^https?://(www\)?example.com [NC]
RewriteRule \.(jpg|jpeg|png|gif)$ - [F,NC]

Redirect www to non-www:

RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.example.com [NC]
RewriteRule ^(.*)$ https://example.com/$1 [L,R=301]

Redirect HTTP to HTTPS:

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Cache control:

<FilesMatch "\.(html|htm|php|cgi|pl)$">
    Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>

<FilesMatch "\.(jpg|jpeg|png|gif|css|js)$">
    Header set Cache-Control "public, max-age=31536000"
</FilesMatch>

Security Hardening

Security is critical for web servers exposed to the internet. These recommendations apply to both nginx and Apache.

General Security Best Practices:

Nginx Security Configuration:

# In nginx.conf
user  www;  # Run as www user, not root

# Hide nginx version in error pages and headers
server_tokens off;

# Disable server version in response headers
more_clear_headers 'Server';

# Limit request methods
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE|OPTIONS)$ ) {
    return 405;
}

# Block access to hidden files (starting with .)
location ~ /\. {
    deny all;
    return 404;
}

# Prevent clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;

# Prevent MIME type sniffing
add_header X-Content-Type-Options "nosniff" always;

# Restrict framing and executable resource types
add_header Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'" always;

# Prevent content-type sniffing for uploads
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|js|pdf|txt|html)$ {
    add_header X-Content-Type-Options "nosniff";
}

Apache Security Configuration:

# Run as non-root user
User www
Group www

# Hide Apache version
ServerTokens Prod
ServerSignature Off

# Disable .htaccess in root directory (if not needed)
<Directory />
    AllowOverride None
    Require all denied
</Directory>

# Protect configuration files
<FilesMatch "^\.ht">
    Require all denied
</FilesMatch>

# Limit request methods
<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

# Disable directory listing
Options -Indexes

# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Content-Security-Policy "frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
Header always set Referrer-Policy "strict-origin-when-cross-origin"

Rate Limiting (Nginx):

# Limit connections from a single IP
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
limit_conn conn_limit 20;

# Limit request rate
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
limit_req zone=req_limit burst=20 nodelay;

# Apply to specific location
location /login {
    limit_req zone=req_limit burst=5;
}

Rate Limiting (Apache):

# Requires mod_evasive or mod_security
# Example with mod_evasive (install via mports)
<IfModule mod_evasive24.c>
    DOSHashTableSize    3097
    DOSPageCount        2
    DOSSiteCount       50
    DOSPageInterval    1
    DOSSiteInterval    1
    DOSBlockingPeriod   60
</IfModule>

File Upload Security:

# Nginx
client_max_body_size 10M;  # Limit upload size
client_body_temp_path /tmp/nginx_upload;

# Apache
LimitRequestBody 10485760  # 10MB limit

Load Balancing

Both nginx and Apache can be configured as load balancers to distribute traffic across multiple backend servers.

Nginx Load Balancing:

# Define upstream servers
upstream backend_servers {
    # Weighted round-robin
    server 192.168.1.101:8000 weight=3;
    server 192.168.1.102:8000;
    server 192.168.1.103:8000;
    
    # Health check
    # check interval=5000 rise=2 fall=3 timeout=3000 type=http;
    # check_http_send "HEAD / HTTP/1.0\r\n\r\n";
    # check_http_expect_alive http_2xx http_3xx;
}

# Round-robin (default)
upstream round_robin {
    server 192.168.1.101:8000;
    server 192.168.1.102:8000;
}

# IP hash (sticky sessions)
upstream sticky_sessions {
    ip_hash;
    server 192.168.1.101:8000;
    server 192.168.1.102:8000;
}

# Least connections
upstream least_conn {
    least_conn;
    server 192.168.1.101:8000;
    server 192.168.1.102:8000;
}

# Use in server block
server {
    listen 80;
    server_name loadbalanced.example.com;
    
    location / {
        proxy_pass http://backend_servers;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Apache Load Balancing (with mod_proxy_balancer):

# Enable required modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so

# Configure balancer
<Proxy balancer://mycluster>
    BalancerMember http://192.168.1.101:8000
    BalancerMember http://192.168.1.102:8000
    BalancerMember http://192.168.1.103:8000
    
    # Sticky session using route
    ProxySet stickysession=ROUTEID
</Proxy>

# Virtual host using load balancer
<VirtualHost *:80>
    ServerName loadbalanced.example.com
    
    ProxyPass / balancer://mycluster/
    ProxyPassReverse / balancer://mycluster/
</VirtualHost>

Health checks and failover:

# Nginx with health checks (requires nginx upstream check module)
upstream backend_with_healthcheck {
    server 192.168.1.101:8000 max_fails=3 fail_timeout=30s;
    server 192.168.1.102:8000 max_fails=3 fail_timeout=30s;
    server 192.168.1.103:8000 backup;
}

# Apache with failover
<Proxy balancer://failover>
    BalancerMember http://192.168.1.101:8000
    BalancerMember http://192.168.1.102:8000
    BalancerMember http://192.168.1.103:8000 status=+H
    ProxySet failonstatus=500,502,503,504
</Proxy>

Monitoring and Logging

Proper monitoring and logging are essential for troubleshooting and performance optimization.

Nginx Logging:

# Custom log format
http {
    log_format custom_log '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" $request_time';
    
    access_log /var/log/nginx/access.log custom_log;
    error_log /var/log/nginx/error.log warn;
}

# Per-server logging
server {
    access_log /var/log/nginx/example.com-access.log;
    error_log /var/log/nginx/example.com-error.log;
}

# Disable logging for specific requests
location /health-check {
    access_log off;
}

Apache Logging:

# Custom log format
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" extended

# Use custom log format
CustomLog /var/log/apache24/access.log extended
ErrorLog /var/log/apache24/error.log

# Per-virtual-host logging
<VirtualHost *:80>
    ServerName example.com
    CustomLog /var/log/apache24/example.com-access.log combined
    ErrorLog /var/log/apache24/example.com-error.log
</VirtualHost>

Log rotation:

# Both nginx and Apache respect newsyslog configuration
# Edit /etc/newsyslog.conf

# Example log rotation entry
/var/log/nginx/*.log       www:www   640  7     *    @T00  JC
/var/log/apache24/*.log   www:www   640  7     *    @T00  JC

Real-time log monitoring:

# Monitor access log in real-time
tail -f /var/log/nginx/access.log

# Filter for errors
tail -f /var/log/nginx/error.log | grep error

# Monitor Apache logs
tail -f /var/log/apache24/access.log

# Count requests per minute
tail -n 1000 /var/log/nginx/access.log | awk '{print $4}' | cut -d: -f2,3 | uniq -c

Access statistics:

# Install log analysis tools
mport install awstats

# Run awstats
awstats -config=nginx -update

Monitoring with htop:

# Install htop
mport install htop

# Monitor web server processes
htop
# Press F5 to sort by CPU usage

Status pages:

# Nginx status page (requires ngx_http_stub_status_module)
location /nginx-status {
    stub_status on;
    access_log off;
    allow 127.0.0.1;
    allow 192.168.1.0/24;
    deny all;
}

# Apache status page (requires mod_status)
<Location /server-status>
    SetHandler server-status
    Require local
    # Or for specific IPs:
    # Require ip 192.168.1.100
</Location>

# Enable extended status
ExtendedStatus On

Troubleshooting

Common Issues and Solutions:

Nginx Troubleshooting

"Address already in use" error:

# Check what's using port 80
netstat -an | grep :80

# Kill the conflicting process
kill -9 [PID]

# Or use sockstat
sockstat -l | grep :80

Configuration syntax errors:

# Test configuration before applying
nginx -t

# If there's an error, it will tell you the file and line number

Permission denied errors:

# Check file permissions
ls -la /usr/local/www/example.com

# Ensure nginx worker process has access
chown -R www:www /usr/local/www/example.com
chmod -R 755 /usr/local/www/example.com

502 Bad Gateway (proxy issues):

# Check if backend server is running
netstat -an | grep 8000

# Check proxy configuration
# Ensure proxy_pass directive points to correct backend

# Test connection to backend
curl http://localhost:8000

403 Forbidden:

# Check directory permissions
ls -la /usr/local/www/example.com

# Check nginx configuration for the location
# Ensure root or alias directives are correct

# Check if index files exist
ls -la /usr/local/www/example.com/index.html
Apache Troubleshooting

"Address already in use" error:

# Check what's using port 80
netstat -an | grep :80

# Stop Apache first
service apache24 stop

# Then start again
service apache24 start

Syntax errors:

# Test configuration
apachectl -t

# If there's an error, it will tell you the file and line number

Permission denied:

# Check file permissions
ls -la /usr/local/www/example.com

# Ensure Apache user has access
chown -R www:www /usr/local/www/example.com
chmod -R 755 /usr/local/www/example.com

# Check SELinux/AppArmor (not typically on MidnightBSD)

Module not loaded:

# Check loaded modules
httpd -M

# Uncomment or add LoadModule directive in httpd.conf
LoadModule rewrite_module modules/mod_rewrite.so

# Restart Apache
service apache24 restart

PHP not working:

# Ensure PHP module is loaded
LoadModule php7_module modules/libphp7.so

# Check PHP handler configuration
<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

# Or for PHP-FPM
# LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
# ProxyPassMatch ^/(.*\.php)$ fcgi://127.0.0.1:9000/usr/local/www/$1

Debugging tools:

# Check open files by Apache
fstat | grep httpd

# Check network connections
netstat -an | grep httpd

# Check Apache modules
httpd -M

# Check nginx processes
ps aux | grep nginx

# Check nginx connections
nginx -T  # Show full configuration with all includes

Debug log level:

# Nginx - set error_log level
error_log /var/log/nginx/error.log debug;

# Apache - set LogLevel
echo "LogLevel debug" >> /usr/local/etc/apache24/httpd.conf
service apache24 restart