Web Server ConfigurationSetting 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.
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 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:
/usr/local/etc/nginx/ - Main configuration directory/usr/local/etc/nginx/nginx.conf - Primary configuration file/usr/local/etc/nginx/conf.d/ - Additional configuration files/usr/local/www/nginx/ - Default web root directory/usr/local/www/nginx/dist/ - Default static files/var/log/nginx/ - Log files (access.log, error.log)/usr/local/sbin/nginx - Nginx binaryThe 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 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;
}
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;.
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";
}
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;
}
}
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 (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:
/usr/local/etc/apache24/ - Main configuration directory/usr/local/etc/apache24/httpd.conf - Primary configuration file/usr/local/etc/apache24/extra/ - Additional configuration files/usr/local/etc/apache24/Includes/ - Included configuration files/usr/local/www/apache24/ - Default document root/usr/local/www/apache24/cgi-bin/ - Default CGI directory/usr/local/www/apache24/data/ - Default htdocs directory/var/log/apache24/ - Log files/usr/local/sbin/apachectl - Apache control scriptThe 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:
httpd.conf - Main configurationextra/httpd-ssl.conf - SSL configurationextra/httpd-vhosts.conf - Virtual hostsextra/httpd-default.conf - Default serverextra/httpd-autoindex.conf - Directory indexingextra/httpd-cgi.conf - CGI configurationCommon 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 |
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>
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"
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>
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 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 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
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>
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
Common Issues and Solutions:
"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
"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