Companion Guide: This guide belongs to the Laravel kits. Every Laravel kit ships these checks built in. Explore Kit #1 (Filament), Kit #2 (Livewire), and Kit #3 (MongoDB).
Track 06
Lesson 21
Secure Deployment
Curated by Umar Farooq
•
18 min read
Quick Answer & Core Takeaway
Deployment security is critical because misconfigured servers are responsible for the majority of data breaches. A secure deployment protects against:
Why Secure Deployment Matters
Deployment security is critical because misconfigured servers are responsible for the majority of data breaches. A secure deployment protects against:
- Unauthorized access to sensitive data
- Code execution through misconfigured permissions
- Data interception via unencrypted connections
- Service disruption from denial of service attacks
- Information leakage through exposed sensitive files
Deployment Security Statistics
- 80% of breaches involve misconfigured servers
- 60% of data breaches occur within minutes of deployment
- 40% of organizations have experienced cloud misconfigurations
- Average cost of deployment-related breach: $4.87 million
HTTPS Configuration
SSL/TLS Certificate Management
Let's Encrypt (Free Certificates) — Certbot Snap (2026 Method)
PHP / Laravel Code
# Install Certbot via snap (recommended — apt package is outdated)
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
# Generate certificate with automatic HTTPS redirect
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
# Or for Nginx:
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Automatic renewal is set up by default via systemd timer
# Verify the renewal timer:
sudo systemctl status snap.certbot.renew.timer
Commercial SSL — ECDSA P-256 (Recommended) or RSA-4096
[SECURE] Hardened Pattern
# [SECURE] Recommended: ECDSA P-256 — Faster, smaller, equally secure
openssl ecparam -genkey -name prime256v1 | openssl ec -out yourdomain-ec.key
openssl req -new -key yourdomain-ec.key -out yourdomain.csr \
-subj "/C=US/ST=State/L=City/O=Company/CN=yourdomain.com"
# Or RSA-4096 (if ECDSA not supported by your CA)
# RSA-2048 is considered marginal by NIST as of 2024 — use 4096 minimum
openssl req -new -newkey rsa:4096 -nodes -keyout yourdomain.key -out yourdomain.csr
# Install certificate files
sudo cp yourdomain.crt /etc/ssl/certs/
sudo cp yourdomain-ec.key /etc/ssl/private/
sudo cp intermediate.crt /etc/ssl/certs/
Apache HTTPS Configuration
PHP / Laravel Code
# /etc/apache2/sites-available/yourdomain-ssl.conf
<VirtualHost *:443>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
DocumentRoot /var/www/yourdomain/public
# SSL Configuration
SSLEngine on
SSLCertificateFile /etc/ssl/certs/yourdomain.crt
SSLCertificateKeyFile /etc/ssl/private/yourdomain.key
SSLCertificateChainFile /etc/ssl/certs/intermediate.crt
# HSTS (HTTP Strict Transport Security)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Security Headers
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# SSL Protocol — TLS 1.3 preferred, TLS 1.2 minimum (TLS 1.0/1.1 disabled)
SSLProtocol -all +TLSv1.3 +TLSv1.2
# TLS 1.3 cipher suites (configured automatically in OpenSSL 1.1.1+)
# TLS 1.2 cipher suites — strong AEAD only
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off # TLS 1.3 ignores this; for TLS 1.2 prefer server order
SSLCompression off # Disable CRIME attack vector
SSLSessionTickets off # Disable for perfect forward secrecy
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
<Directory /var/www/yourdomain/public>
Options -Indexes -FollowSymLinks
AllowOverride All
Require all granted
# Additional security for Laravel
php_value upload_max_filesize 10M
php_value post_max_size 12M
php_value memory_limit 256M
</Directory>
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias www.yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>
Nginx HTTPS Configuration
[SECURE] Hardened Pattern
# /etc/nginx/sites-available/yourdomain
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL Configuration
ssl_certificate /etc/ssl/certs/yourdomain.crt;
ssl_certificate_key /etc/ssl/private/yourdomain.key;
ssl_trusted_certificate /etc/ssl/certs/intermediate.crt;
# SSL Protocols and Ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Security Headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Root directory
root /var/www/yourdomain/public;
index index.php index.html;
# Hide nginx version
server_tokens off;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Secure file access
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Restrict access to sensitive files
location ~ \.(env|log|htaccess|htpasswd)$ {
deny all;
return 444;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
SSL/TLS Best Practices
Certificate Configuration
PHP / Laravel Code
# Test SSL configuration
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
# Check certificate details
openssl x509 -in yourdomain.crt -text -noout
# Verify certificate chain
openssl verify -CAfile intermediate.crt yourdomain.crt
SSL Labs Testing
PHP / Laravel Code
# Test SSL configuration quality
# Visit: https://www.ssllabs.com/ssltest/
Server File Permissions
Linux File Permissions
Understanding Permissions
PHP / Laravel Code
# File permission format: rwxrwxrwx (owner, group, others)
# r = read (4), w = write (2), x = execute (1)
# View permissions
ls -la /var/www/
# Change ownership
sudo chown -R www-data:www-data /var/www/yourdomain
# Set directory permissions (755)
sudo find /var/www/yourdomain -type d -exec chmod 755 {} \;
# Set file permissions (644)
sudo find /var/www/yourdomain -type f -exec chmod 644 {} \;
Laravel-Specific Permissions
PHP / Laravel Code
# Laravel storage and bootstrap cache permissions
sudo chown -R www-data:www-data /var/www/yourdomain/storage
sudo chown -R www-data:www-data /var/www/yourdomain/bootstrap/cache
sudo chmod -R 775 /var/www/yourdomain/storage
sudo chmod -R 775 /var/www/yourdomain/bootstrap/cache
# Make artisan executable
sudo chmod +x /var/www/yourdomain/artisan
Secure Directory Structure
PHP / Laravel Code
/var/www/yourdomain/
├── app/ # 755
├── bootstrap/ # 755
│ └── cache/ # 775
├── config/ # 755
├── database/ # 755
├── public/ # 755
│ ├── index.php # 644
│ └── .htaccess # 644
├── resources/ # 755
├── routes/ # 755
├── storage/ # 775
│ ├── app/ # 775
│ ├── framework/ # 775
│ └── logs/ # 775
├── tests/ # 755
├── vendor/ # 755
├── artisan # 755
├── composer.json # 644
├── composer.lock # 644
├── package.json # 644
├── webpack.mix.js # 644
├── .env.example # 644
└── .env # 600 (restrictive!)
File Permission Audit Script
[INSECURE] Vulnerable Pattern
#!/bin/bash
# secure-permissions.sh - Audit and fix file permissions
TARGET_DIR="/var/www/yourdomain"
WEB_USER="www-data"
WEB_GROUP="www-data"
echo " Auditing file permissions in $TARGET_DIR"
# Check .env file permissions
ENV_FILE="$TARGET_DIR/.env"
if [ -f "$ENV_FILE" ]; then
PERMS=$(stat -c "%a" "$ENV_FILE")
if [ "$PERMS" != "600" ]; then
echo "[INSECURE] .env file has insecure permissions: $PERMS (should be 600)"
chmod 600 "$ENV_FILE"
echo "[SECURE] Fixed .env permissions"
fi
fi
# Check storage directory permissions
STORAGE_DIR="$TARGET_DIR/storage"
if [ -d "$STORAGE_DIR" ]; then
find "$STORAGE_DIR" -type d -exec chmod 775 {} \;
find "$STORAGE_DIR" -type f -exec chmod 664 {} \;
chown -R "$WEB_USER:$WEB_GROUP" "$STORAGE_DIR"
echo "[SECURE] Fixed storage directory permissions"
fi
# Check bootstrap cache permissions
BOOTSTRAP_CACHE="$TARGET_DIR/bootstrap/cache"
if [ -d "$BOOTSTRAP_CACHE" ]; then
chmod 775 "$BOOTSTRAP_CACHE"
chown -R "$WEB_USER:$WEB_GROUP" "$BOOTSTRAP_CACHE"
echo "[SECURE] Fixed bootstrap cache permissions"
fi
# Find world-writable files (security risk)
echo " Checking for world-writable files..."
WORLD_WRITABLE=$(find "$TARGET_DIR" -type f -perm -002 2>/dev/null)
if [ -n "$WORLD_WRITABLE" ]; then
echo "[INSECURE] Found world-writable files:"
echo "$WORLD_WRITABLE"
echo "Consider removing world write permissions"
fi
# Find files with incorrect ownership
echo " Checking file ownership..."
INCORRECT_OWNER=$(find "$TARGET_DIR" -not -user "$WEB_USER" -type f \( -name "*.php" -o -name "*.log" \) 2>/dev/null | head -10)
if [ -n "$INCORRECT_OWNER" ]; then
echo "Warning: Found files not owned by $WEB_USER:"
echo "$INCORRECT_OWNER"
fi
echo " Permission audit completed!"
Hiding Sensitive Files
Web Server Configuration
Apache .htaccess
PHP / Laravel Code
# /var/www/yourdomain/public/.htaccess
# Hide sensitive files
<FilesMatch "\.(env|log|htaccess|htpasswd|ini|conf|bak|backup|old)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
# Hide version control files
<FilesMatch "(\.git|\.svn|\.hg)">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
# Hide backup files
<FilesMatch "\.(bak|backup|old|orig|tmp|temp)$">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
# Hide PHP error logs
<Files "error_log">
Order Allow,Deny
Deny from all
Require all denied
</Files>
# Hide composer files
<FilesMatch "(composer\.json|composer\.lock)">
Order Allow,Deny
Deny from all
Require all denied
</FilesMatch>
Nginx Configuration
PHP / Laravel Code
# /etc/nginx/sites-available/yourdomain
server {
# ... SSL configuration ...
# Hide sensitive files
location ~ /\.(env|git|svn|hg) {
deny all;
return 444;
}
location ~ \.(log|htaccess|htpasswd|ini|conf|bak|backup|old|orig|tmp|temp)$ {
deny all;
return 444;
}
location ~ (composer\.json|composer\.lock|package\.json|webpack\.mix\.js)$ {
deny all;
return 444;
}
location ~ \.php$ {
# ... PHP configuration ...
# Prevent access to certain PHP files
location ~ /(artisan|console)$ {
deny all;
return 444;
}
}
}
Laravel-Specific File Protection
PHP / Laravel Code
<?php
// routes/web.php - Additional file protection
// Prevent access to sensitive Laravel files
Route::get('/artisan', function () {
abort(404);
});
Route::get('/console', function () {
abort(404);
});
// Prevent access to composer files
Route::get('/composer.{json,lock}', function () {
abort(404);
});
// Prevent access to environment files
Route::get('/.env{,.example}', function () {
abort(404);
});
File Location Strategy
[SECURE] Hardened Pattern
# Secure file placement strategy
# 1. Public files (accessible via web)
/var/www/yourdomain/public/
├── index.php
├── assets/
├── uploads/ # User-uploaded files (with access control)
└── .htaccess
# 2. Application files (not web-accessible)
/var/www/yourdomain/
├── app/
├── config/
├── database/
├── resources/
├── routes/
├── tests/
├── vendor/
├── composer.json
├── composer.lock
└── artisan
# 3. Sensitive files (restricted access)
/var/www/yourdomain/
├── .env # 600 permissions
├── storage/logs/ # 775 permissions
└── bootstrap/cache/ # 775 permissions
# 4. Backup files (outside web root)
/var/backups/yourdomain/
├── database/
└── files/
Basic Firewall Rules
UFW (Uncomplicated Firewall)
PHP / Laravel Code
# Install UFW
sudo apt install ufw
# Enable UFW
sudo ufw enable
# Allow SSH (before enabling firewall!)
sudo ufw allow ssh
sudo ufw allow 22
# Allow HTTP and HTTPS
sudo ufw allow 80
sudo ufw allow 443
# Allow MySQL (if needed from specific IPs)
sudo ufw allow from 192.168.1.0/24 to any port 3306
# Deny everything else by default
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Check status
sudo ufw status verbose
# View numbered rules
sudo ufw status numbered
iptables (Advanced Configuration)
[SECURE] Hardened Pattern
#!/bin/bash
# secure-firewall.sh - Configure iptables firewall
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback interface
iptables -A INPUT -i lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (rate limited)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
# Allow HTTPS
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow MySQL from specific IPs
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 3306 -j ACCEPT
# Allow ping
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "Dropped: " --log-level 4
# Save rules
sudo iptables-save > /etc/iptables/rules.v4
# Make persistent
sudo apt install iptables-persistent
sudo systemctl enable netfilter-persistent
Fail2Ban Integration
PHP / Laravel Code
# Install Fail2Ban
sudo apt install fail2ban
# Configure SSH protection
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# Edit /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
# Configure Apache protection
[apache-noscript]
enabled = true
port = http,https
filter = apache-noscript
logpath = /var/log/apache2/error.log
maxretry = 6
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
# Restart Fail2Ban
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status
Application-Level Firewall
PHP / Laravel Code
<?php
class ApplicationFirewall
{
private array $blacklist = [];
private array $whitelist = [];
public function __construct()
{
// Load IP blacklists
$this->loadIPLists();
}
public function checkRequest(): bool
{
$clientIP = $_SERVER['REMOTE_ADDR'];
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$requestUri = $_SERVER['REQUEST_URI'] ?? '';
// Check IP blacklist
if ($this->isBlacklisted($clientIP)) {
$this->logBlockedRequest($clientIP, 'IP blacklist');
return false;
}
// Check suspicious user agents
if ($this->isSuspiciousUserAgent($userAgent)) {
$this->logBlockedRequest($clientIP, 'Suspicious user agent');
return false;
}
// Check for malicious patterns in URI
if ($this->containsMaliciousPatterns($requestUri)) {
$this->logBlockedRequest($clientIP, 'Malicious URI pattern');
return false;
}
return true;
}
private function loadIPLists(): void
{
// Load from files or database
$this->blacklist = [
'192.168.1.100', // Example blocked IP
'10.0.0.1',
];
$this->whitelist = [
'192.168.1.0/24', // Local network
];
}
private function isBlacklisted(string $ip): bool
{
return in_array($ip, $this->blacklist);
}
private function isSuspiciousUserAgent(string $userAgent): bool
{
$suspiciousPatterns = [
'/sqlmap/i',
'/nikto/i',
'/dirbuster/i',
'/acunetix/i',
'/nessus/i',
];
foreach ($suspiciousPatterns as $pattern) {
if (preg_match($pattern, $userAgent)) {
return true;
}
}
return false;
}
private function containsMaliciousPatterns(string $uri): bool
{
$maliciousPatterns = [
'/\.\./', // Directory traversal
'/<script/i', // XSS attempts
'/union\s+select/i', // SQL injection
'/eval\(/i', // Code injection
'/base64_decode/i', // Obfuscation
];
foreach ($maliciousPatterns as $pattern) {
if (preg_match($pattern, $uri)) {
return true;
}
}
return false;
}
private function logBlockedRequest(string $ip, string $reason): void
{
$logEntry = sprintf(
"[FIREWALL BLOCK] IP: %s, Reason: %s, URI: %s, User-Agent: %s, Time: %s\n",
$ip,
$reason,
$_SERVER['REQUEST_URI'] ?? '',
$_SERVER['HTTP_USER_AGENT'] ?? '',
date('Y-m-d H:i:s')
);
error_log($logEntry);
}
}
// Usage in application bootstrap
$firewall = new ApplicationFirewall();
if (!$firewall->checkRequest()) {
http_response_code(403);
echo "Access Denied";
exit;
}
Deployment Security Checklist
HTTPS Configuration
File Permissions
File Protection
Firewall Configuration
Server Hardening
Automated Deployment Scripts
Secure Deployment Script
[INSECURE] Vulnerable Pattern
#!/bin/bash
# secure-deploy.sh
set -e # Exit on any error
echo " Starting secure deployment..."
# Pre-deployment checks
echo " Running pre-deployment checks..."
# Check if running as root (dangerous)
if [ "$EUID" -eq 0 ]; then
echo "[INSECURE] Do not run deployment as root"
exit 1
fi
# Check disk space
REQUIRED_SPACE=1000000 # 1GB in KB
AVAILABLE_SPACE=$(df /var/www | tail -1 | awk '{print $4}')
if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_SPACE" ]; then
echo "[INSECURE] Insufficient disk space"
exit 1
fi
# Backup current deployment
echo " Creating backup..."
BACKUP_DIR="/var/backups/$(date +%Y%m%d_%H%M%S)"
sudo mkdir -p "$BACKUP_DIR"
sudo cp -r /var/www/yourdomain "$BACKUP_DIR/"
# Deploy application
echo " Deploying application..."
sudo cp -r /tmp/deployment/* /var/www/yourdomain/
# Set secure permissions
echo " Setting secure permissions..."
sudo chown -R www-data:www-data /var/www/yourdomain
sudo find /var/www/yourdomain -type f -exec chmod 644 {} \;
sudo find /var/www/yourdomain -type d -exec chmod 755 {} \;
sudo chmod 600 /var/www/yourdomain/.env
sudo chmod 775 /var/www/yourdomain/storage
sudo chmod 775 /var/www/yourdomain/bootstrap/cache
# Install dependencies
echo " Installing dependencies..."
cd /var/www/yourdomain
composer install --no-dev --optimize-autoloader
npm ci --production
# Run database migrations
echo " Running migrations..."
php artisan migrate --force
# Clear and cache configuration
echo " Optimizing application..."
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
# Install Node.js dependencies and build assets
echo " Building assets..."
npm run production
# Run tests
echo " Running tests..."
if ! php artisan test --no-coverage; then
echo "[INSECURE] Tests failed - rolling back..."
sudo rm -rf /var/www/yourdomain
sudo mv "$BACKUP_DIR/yourdomain" /var/www/
exit 1
fi
# Health check
echo " Running health checks..."
if curl -f -s http://localhost/health-check > /dev/null; then
echo "[SECURE] Application health check passed"
else
echo "[INSECURE] Application health check failed - rolling back..."
sudo rm -rf /var/www/yourdomain
sudo mv "$BACKUP_DIR/yourdomain" /var/www/
exit 1
fi
# Clean up old backups (keep last 5)
echo " Cleaning up old backups..."
cd /var/backups
ls -t | tail -n +6 | xargs -r sudo rm -rf
# Restart services
echo " Restarting services..."
sudo systemctl reload nginx
sudo systemctl reload php8.2-fpm
# Log deployment
echo "$(date): Deployment completed successfully" >> /var/log/deployments.log
echo " Secure deployment completed successfully!"
Rollback Script
[INSECURE] Vulnerable Pattern
#!/bin/bash
# rollback.sh
echo " Starting rollback..."
# Find latest backup
LATEST_BACKUP=$(ls -t /var/backups | head -1)
if [ -z "$LATEST_BACKUP" ]; then
echo "[INSECURE] No backup found"
exit 1
fi
echo " Rolling back to: $LATEST_BACKUP"
# Stop services
sudo systemctl stop nginx
sudo systemctl stop php8.2-fpm
# Restore backup
sudo rm -rf /var/www/yourdomain
sudo cp -r "/var/backups/$LATEST_BACKUP" /var/www/yourdomain
# Restart services
sudo systemctl start php8.2-fpm
sudo systemctl start nginx
# Health check
if curl -f -s http://localhost/health-check > /dev/null; then
echo "[SECURE] Rollback successful"
else
echo "[INSECURE] Rollback failed - manual intervention required"
exit 1
fi
Monitoring and Alerting
Server Monitoring Setup
PHP / Laravel Code
# Install monitoring tools
sudo apt install nagios-nrpe-server nagios-plugins
sudo apt install prometheus node-exporter
# Configure log monitoring
sudo apt install logwatch
sudo logwatch --detail high --mailto admin@yourdomain.com --range today
# Set up automated security scans
sudo apt install lynis
sudo lynis audit system --cronjob
Deployment Monitoring
PHP / Laravel Code
<?php
class DeploymentMonitor
{
public static function logDeployment(array $details): void
{
$logEntry = [
'timestamp' => date('c'),
'type' => 'DEPLOYMENT',
'version' => $details['version'] ?? 'unknown',
'environment' => $details['environment'] ?? 'production',
'user' => $details['user'] ?? 'automated',
'duration' => $details['duration'] ?? 0,
'status' => $details['status'] ?? 'unknown',
'commit_hash' => $details['commit_hash'] ?? null,
];
// Log to file
$jsonEntry = json_encode($logEntry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
file_put_contents('/var/log/deployments.log', $jsonEntry . "\n", FILE_APPEND | LOCK_EX);
// Send alert for failed deployments
if (($details['status'] ?? 'unknown') === 'failed') {
self::sendFailureAlert($details);
}
}
public static function checkPostDeploymentHealth(): array
{
$issues = [];
// Check file permissions
if (!self::checkFilePermissions()) {
$issues[] = 'File permissions incorrect after deployment';
}
// Check database connectivity
if (!self::checkDatabaseConnection()) {
$issues[] = 'Database connection failed';
}
// Check application responsiveness
if (!self::checkApplicationHealth()) {
$issues[] = 'Application health check failed';
}
return $issues;
}
private static function checkFilePermissions(): bool
{
$envFile = '/var/www/yourdomain/.env';
return file_exists($envFile) && decoct(fileperms($envFile) & 0777) === '600';
}
private static function checkDatabaseConnection(): bool
{
try {
$pdo = new PDO(
"mysql:host=" . getenv('DB_HOST') . ";dbname=" . getenv('DB_DATABASE'),
getenv('DB_USERNAME'),
getenv('DB_PASSWORD')
);
return true;
} catch (PDOException $e) {
return false;
}
}
private static function checkApplicationHealth(): bool
{
$context = stream_context_create([
'http' => [
'timeout' => 5,
'ignore_errors' => true
]
]);
$response = file_get_contents('http://localhost/health-check', false, $context);
return $response !== false && strpos($response, 'OK') !== false;
}
private static function sendFailureAlert(array $details): void
{
$message = " Deployment Failed\n\n";
$message .= "Version: {$details['version']}\n";
$message .= "Environment: {$details['environment']}\n";
$message .= "Error: {$details['error'] ?? 'Unknown error'}\n";
$message .= "Time: " . date('Y-m-d H:i:s') . "\n";
// Send email alert
mail(
getenv('ADMIN_EMAIL') ?: 'admin@yourdomain.com',
'Deployment Failure Alert',
$message
);
}
}
Summary: Secure Deployment Rules
- Always use HTTPS - Redirect HTTP traffic and use strong SSL/TLS
- Set restrictive file permissions - Follow principle of least privilege
- Hide sensitive files - Block access to .env, logs, and configuration files
- Configure firewall properly - Close unnecessary ports and implement rate limiting
- Use automated deployment - Implement CI/CD with security checks
- Monitor deployments - Log deployment activities and monitor health
- Have rollback procedures - Be able to quickly revert problematic deployments
- Regular security audits - Scan for vulnerabilities after deployment
- Implement least privilege - Run services with minimal required permissions
- Keep systems updated - Apply security patches regularly
Next Steps
Now that you understand secure deployment, explore:
- API Security Basics - Secure API authentication and rate limiting
- Error Handling & Logging - Secure error management
- Dependency Security - Secure package management
Remember: Security is not just about code - deployment configuration is equally critical. A perfectly secure application can be compromised through misconfigured servers!
AI Assistant Shortcuts
Ask AI about this topic