Server Configuration Guide
Detailed web server configuration for Apache and Nginx, including security, performance, and Laravel-specific optimizations.
Table of Contents
- TOC
Apache Configuration
Basic Virtual Host Setup
The deployment/apache.conf
and deployment/apache-windows.conf
files provide ready-to-use configurations. Here’s how to customize them for your environment:
Linux/Unix Setup
<VirtualHost *:80>
ServerName inventory-app.your-domain.com
ServerAlias www.inventory-app.your-domain.com
DocumentRoot /var/www/inventory-app/public
<Directory /var/www/inventory-app/public>
AllowOverride All
Require all granted
Options -Indexes +FollowSymLinks
# Laravel URL rewriting
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</Directory>
# Logging
ErrorLog ${APACHE_LOG_DIR}/inventory-app-error.log
CustomLog ${APACHE_LOG_DIR}/inventory-app-access.log combined
</VirtualHost>
Windows Setup
<VirtualHost *:80>
ServerName inventory-app.your-domain.com
DocumentRoot "C:/inetpub/wwwroot/inventory-app/public"
<Directory "C:/inetpub/wwwroot/inventory-app/public">
AllowOverride All
Require all granted
Options -Indexes +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</Directory>
ErrorLog "C:/Apache24/logs/inventory-app-error.log"
CustomLog "C:/Apache24/logs/inventory-app-access.log" combined
</VirtualHost>
SSL/HTTPS Configuration
Production SSL Setup
<VirtualHost *:443>
ServerName inventory-app.your-domain.com
DocumentRoot "/var/www/inventory-app/public"
# SSL Configuration
SSLEngine on
SSLCertificateFile /path/to/certificate.crt
SSLCertificateKeyFile /path/to/private.key
SSLCertificateChainFile /path/to/chain.crt
# Modern SSL configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
# Directory configuration (same as HTTP)
<Directory "/var/www/inventory-app/public">
AllowOverride All
Require all granted
Options -Indexes +FollowSymLinks
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [QSA,L]
</Directory>
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName inventory-app.your-domain.com
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
Performance Optimization
Enable Compression
# Load required modules
LoadModule deflate_module modules/mod_deflate.so
LoadModule filter_module modules/mod_filter.so
# Compression configuration
<Location />
SetOutputFilter DEFLATE
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \
\.(?:exe|t?gz|zip|bz2|sit|rar)$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \
\.pdf$ no-gzip dont-vary
SetEnvIfNoCase Request_URI \
\.avi$ no-gzip dont-vary
</Location>
Static File Caching
# Load expires module
LoadModule expires_module modules/mod_expires.so
# Cache static assets
<LocationMatch "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$">
ExpiresActive On
ExpiresDefault "access plus 1 year"
Header append Cache-Control "public, immutable"
</LocationMatch>
# Cache HTML with shorter expiry
<LocationMatch "\.html$">
ExpiresActive On
ExpiresDefault "access plus 1 hour"
Header append Cache-Control "public"
</LocationMatch>
Security Configuration
Security Headers
# Load headers module
LoadModule headers_module modules/mod_headers.so
# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'"
File Access Restrictions
# Prevent access to sensitive files
<Files ~ "^\.">
Require all denied
</Files>
<FilesMatch "\.(md|json|lock|yml|yaml|env|log)$">
Require all denied
</FilesMatch>
# Prevent access to directories
<DirectoryMatch "(storage|bootstrap/cache|vendor|node_modules|\.git)">
Require all denied
</DirectoryMatch>
PHP Configuration for Apache
Using PHP-FPM (Recommended)
# Load proxy modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
# PHP-FPM configuration
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php8.2-fpm.sock|fcgi://localhost"
</FilesMatch>
# Windows PHP-FPM
<FilesMatch \.php$>
SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
Using PHP Module (Alternative)
# Load PHP module
LoadModule php_module modules/libphp8.so
# PHP configuration
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>
# PHP ini settings
php_value upload_max_filesize 64M
php_value post_max_size 64M
php_value memory_limit 256M
php_value max_execution_time 300
Nginx Configuration
Basic Server Block
The deployment/nginx.conf
file provides a complete configuration. Here’s the core setup:
server {
listen 80;
listen [::]:80;
server_name inventory-app.your-domain.com www.inventory-app.your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name inventory-app.your-domain.com www.inventory-app.your-domain.com;
root /var/www/inventory-app/public;
index index.php index.html index.htm;
# SSL Configuration
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# Laravel URL rewriting
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
SSL Configuration
Modern SSL Setup
# SSL Configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/chain.crt;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Performance Optimization
Gzip Compression
# Gzip Settings
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
Static File Caching
# Cache static assets
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options nosniff;
access_log off;
}
# Cache HTML files
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public";
}
Buffer Optimization
# Buffer settings
client_body_buffer_size 128k;
client_max_body_size 64m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
output_buffers 1 32k;
postpone_output 1460;
Security Configuration
Security Headers
# Security headers
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options DENY always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Content Security Policy
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'" always;
File Access Restrictions
# Prevent access to sensitive files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
location ~* \.(md|json|lock|yml|yaml|env|log)$ {
deny all;
access_log off;
log_not_found off;
}
# Prevent access to directories
location ~* /(storage|bootstrap/cache|vendor|node_modules|\.git)/ {
deny all;
access_log off;
log_not_found off;
}
Rate Limiting
API Rate Limiting
# In http block (nginx.conf)
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# In server block
location /api {
limit_req zone=api burst=20 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
location /api/login {
limit_req zone=login burst=5 nodelay;
try_files $uri $uri/ /index.php?$query_string;
}
}
PHP-FPM Configuration
Pool Configuration
Create a dedicated pool for the application:
; /etc/php/8.2/fpm/pool.d/inventory-app.conf
[inventory-app]
user = www-data
group = www-data
listen = /var/run/php/php8.2-fpm-inventory-app.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Process management
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 1000
; Resource limits
request_terminate_timeout = 300
rlimit_files = 65536
rlimit_core = 0
; Logging
php_admin_value[error_log] = /var/log/php8.2-fpm-inventory-app.log
php_admin_flag[log_errors] = on
; Environment variables
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[TMP] = /tmp
env[TMPDIR] = /tmp
env[TEMP] = /tmp
Performance Tuning
; Performance settings
pm.status_path = /status
ping.path = /ping
ping.response = pong
; Process limits
pm.process_idle_timeout = 10s
pm.max_requests = 1000
; Memory limits
php_admin_value[memory_limit] = 256M
php_admin_value[max_execution_time] = 300
php_admin_value[max_input_time] = 300
Load Balancing
Nginx Load Balancer
upstream inventory_app {
least_conn;
server 192.168.1.10:80 weight=3;
server 192.168.1.11:80 weight=2;
server 192.168.1.12:80 backup;
}
server {
listen 80;
server_name inventory-app.your-domain.com;
location / {
proxy_pass http://inventory_app;
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;
}
}
Apache Load Balancer
# Load balancing modules
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
# Define balancer
<Proxy balancer://inventory-app>
BalancerMember http://192.168.1.10:80 route=app1
BalancerMember http://192.168.1.11:80 route=app2
BalancerMember http://192.168.1.12:80 route=app3 status=+H
ProxySet lbmethod byrequests
</Proxy>
<VirtualHost *:80>
ServerName inventory-app.your-domain.com
ProxyPass / balancer://inventory-app/
ProxyPassReverse / balancer://inventory-app/
</VirtualHost>
Monitoring and Logging
Access Log Analysis
Apache Log Format
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\" %D" combined_with_time
CustomLog /var/log/apache2/inventory-app-access.log combined_with_time
Nginx Log Format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/inventory-app-access.log main;
Error Monitoring
Log Rotation
# /etc/logrotate.d/inventory-app
/var/log/apache2/inventory-app-*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 www-data www-data
postrotate
systemctl reload apache2
endscript
}
Health Checks
Nginx Health Check
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
Apache Health Check
<Location "/health">
SetHandler server-info
Require local
</Location>
Security Best Practices
Firewall Configuration
UFW (Ubuntu)
# Allow SSH
sudo ufw allow 22
# Allow HTTP and HTTPS
sudo ufw allow 80
sudo ufw allow 443
# Deny all other incoming traffic
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Enable firewall
sudo ufw enable
Windows Firewall
# Allow HTTP
netsh advfirewall firewall add rule name="HTTP" dir=in action=allow protocol=TCP localport=80
# Allow HTTPS
netsh advfirewall firewall add rule name="HTTPS" dir=in action=allow protocol=TCP localport=443
Fail2Ban Configuration
# /etc/fail2ban/jail.local
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/inventory-app-error.log
maxretry = 5
bantime = 3600
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/inventory-app-error.log
findtime = 600
bantime = 7200
maxretry = 10
Troubleshooting
Common Issues
- 502 Bad Gateway (Nginx)
- Check PHP-FPM status:
systemctl status php8.2-fpm
- Verify socket permissions
- Check PHP-FPM logs
- Check PHP-FPM status:
- 500 Internal Server Error
- Check web server error logs
- Verify file permissions
- Check PHP error logs
- Static Files Not Loading
- Verify build process completed
- Check file permissions
- Verify web server configuration
Performance Issues
- Slow Response Times
- Enable OPcache
- Optimize database queries
- Enable compression
- High Memory Usage
- Tune PHP-FPM pool settings
- Increase server resources
- Optimize application code
Next Steps
- 📖 Configuration Guide - Application configuration
- 💻 Development Setup - Local development
- 🔒 Security Guide - Security best practices
- 📊 Monitoring - Application monitoring