SSL Setup - luckydeva03/desa_karangrejo GitHub Wiki
Panduan lengkap setup SSL certificate untuk Website Desa Karangrejo.
SSL (Secure Socket Layer) atau TLS (Transport Layer Security) adalah teknologi keamanan yang mengenkripsi koneksi antara browser dan server. Panduan ini menjelaskan cara setup SSL certificate untuk website desa.
- Enkripsi data dalam transit
- Perlindungan terhadap man-in-the-middle attacks
- Keamanan login dan form submissions
- Perlindungan informasi sensitif warga
- Google ranking factor - website HTTPS mendapat prioritas
- Trust indicator - browser menampilkan ikon gembok
- User confidence - pengunjung lebih percaya
- Modern standard - semua website seharusnya menggunakan HTTPS
- Standar keamanan pemerintah
- Best practices untuk website resmi
- GDPR compliance jika ada data personal
- PCI DSS jika ada payment processing
✅ Gratis dengan Let's Encrypt
✅ Proses otomatis
✅ Validasi cepat (menit)
✅ Cocok untuk website desa
❌ Tidak menampilkan nama organisasi
💰 Berbayar ($50-200/tahun)
🔍 Validasi organisasi required
⏰ Proses 1-3 hari
✅ Menampilkan nama organisasi
✅ Lebih terpercaya untuk bisnis
💰💰 Mahal ($200-1000/tahun)
🔍🔍 Validasi ketat organisasi
⏰ Proses 1-2 minggu
✅ Address bar berwarna hijau
✅ Nama organisasi di browser
❌ Overkill untuk website desa
- Certificate Authority gratis dan terbuka
- Automated certificate issuance dan renewal
- Widely trusted - didukung semua browser modern
- 90-day validity dengan auto-renewal
- Perfect untuk website desa
# Pastikan domain sudah pointing ke server
dig karangrejo.desa.id +short
# Harus menampilkan IP server Anda
# Pastikan port 80 dan 443 terbuka
sudo ufw status
sudo netstat -tlnp | grep :80
sudo netstat -tlnp | grep :443
# Ubuntu/Debian
sudo apt update
sudo apt install -y certbot python3-certbot-nginx
# CentOS/RHEL
sudo yum install -y certbot python3-certbot-nginx
# Verify installation
certbot --version
# Method 1: Nginx Plugin (Automatic)
sudo certbot --nginx -d karangrejo.desa.id -d www.karangrejo.desa.id
# Method 2: Webroot (Manual)
sudo certbot certonly --webroot -w /var/www/desa-karangrejo/public -d karangrejo.desa.id -d www.karangrejo.desa.id
# Method 3: Standalone (Temporary server)
sudo systemctl stop nginx
sudo certbot certonly --standalone -d karangrejo.desa.id -d www.karangrejo.desa.id
sudo systemctl start nginx
# Certbot akan meminta informasi:
Enter email address (used for urgent renewal and security notices): [email protected]
Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf
(A)gree/(C)ancel: A
Would you be willing to share your email address with the Electronic Frontier Foundation
(Y)es/(N)o: N
# Certbot akan otomatis:
# 1. Verify domain ownership
# 2. Generate certificate
# 3. Update Nginx configuration
# 4. Test HTTPS access
# Check certificate details
sudo certbot certificates
# Test HTTPS access
curl -I https://karangrejo.desa.id
# Check SSL labs rating (online)
# https://www.ssllabs.com/ssltest/analyze.html?d=karangrejo.desa.id
# /etc/nginx/sites-available/karangrejo.desa.id
server {
listen 80;
server_name karangrejo.desa.id www.karangrejo.desa.id;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name karangrejo.desa.id www.karangrejo.desa.id;
root /var/www/desa-karangrejo/public;
# SSL Configuration
ssl_certificate /etc/letsencrypt/live/karangrejo.desa.id/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/karangrejo.desa.id/privkey.pem;
# SSL Security Settings
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/karangrejo.desa.id/chain.pem;
# Security Headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Rest of your configuration...
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
}
# /etc/nginx/conf.d/ssl.conf
# 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# SSL Session Settings
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security Headers Template
map $scheme $hsts_header {
https "max-age=31536000; includeSubDomains; preload";
}
add_header Strict-Transport-Security $hsts_header always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# Dry run (test without actually renewing)
sudo certbot renew --dry-run
# Expected output:
# Congratulations, all renewals succeeded
# Option 1: Cron Job
sudo crontab -e
# Add this line (runs twice daily):
0 12 * * * /usr/bin/certbot renew --quiet
# Option 2: Systemd Timer (modern approach)
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# Check timer status
sudo systemctl status certbot.timer
# Create renewal hook script
sudo nano /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh
#!/bin/bash
# /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh
# Test nginx configuration
nginx -t
# If test passes, reload nginx
if [ $? -eq 0 ]; then
systemctl reload nginx
echo "$(date): Nginx reloaded after SSL renewal" >> /var/log/ssl-renewal.log
else
echo "$(date): Nginx configuration test failed" >> /var/log/ssl-renewal.log
exit 1
fi
# Make script executable
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh
# SSL Labs Test (Comprehensive)
https://www.ssllabs.com/ssltest/analyze.html?d=karangrejo.desa.id
# Target Grade: A or A+
# Mozilla Observatory
https://observatory.mozilla.org/analyze/karangrejo.desa.id
# Target Score: 90+
# Test SSL certificate
openssl s_client -connect karangrejo.desa.id:443 -servername karangrejo.desa.id
# Check certificate expiry
echo | openssl s_client -connect karangrejo.desa.id:443 2>/dev/null | \
openssl x509 -noout -dates
# Test specific TLS version
openssl s_client -connect karangrejo.desa.id:443 -tls1_2 -servername karangrejo.desa.id
# Check in different browsers:
# - Chrome: Look for green padlock
# - Firefox: Look for green padlock
# - Safari: Look for green padlock
# - Edge: Look for green padlock
# Check certificate details:
# Click on padlock → Certificate → Details
# Certificate Pinning (for mobile apps)
# Get certificate fingerprint
openssl x509 -in /etc/letsencrypt/live/karangrejo.desa.id/cert.pem -pubkey -noout | \
openssl rsa -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64
// Service Worker HTTPS requirement
// PWA features only work over HTTPS
// manifest.json requires HTTPS
{
"name": "Website Desa Karangrejo",
"start_url": "https://karangrejo.desa.id/",
"display": "standalone"
}
// Problem: HTTP resources on HTTPS page
// Solution: Update all URLs to HTTPS
// Bad
<img src="http://example.com/image.jpg">
<script src="http://example.com/script.js"></script>
// Good
<img src="https://example.com/image.jpg">
<script src="https://example.com/script.js"></script>
// Laravel: Force HTTPS URLs
// In AppServiceProvider
public function boot()
{
if (config('app.env') === 'production') {
URL::forceScheme('https');
}
}
# Problem: Incomplete certificate chain
# Check chain
openssl s_client -connect karangrejo.desa.id:443 -showcerts
# Solution: Use fullchain.pem instead of cert.pem
ssl_certificate /etc/letsencrypt/live/karangrejo.desa.id/fullchain.pem;
# Check renewal logs
sudo journalctl -u certbot.timer
# Common issues:
# - Port 80 blocked
# - Webroot path incorrect
# - DNS not pointing to server
# Manual renewal with verbose output
sudo certbot renew --verbose
# Test nginx configuration
sudo nginx -t
# Common SSL errors:
# - Wrong certificate path
# - Missing ssl_protocols
# - Conflicting server blocks
# Reload nginx after fixing
sudo systemctl reload nginx
#!/bin/bash
# emergency-ssl-fix.sh
echo "🚨 Emergency SSL Recovery"
# 1. Stop nginx
sudo systemctl stop nginx
# 2. Obtain new certificate
sudo certbot certonly --standalone -d karangrejo.desa.id -d www.karangrejo.desa.id --force-renewal
# 3. Update nginx configuration
sudo nano /etc/nginx/sites-available/karangrejo.desa.id
# 4. Test configuration
sudo nginx -t
# 5. Start nginx
if [ $? -eq 0 ]; then
sudo systemctl start nginx
echo "✅ SSL recovery completed"
else
echo "❌ Nginx configuration error"
exit 1
fi
# Check certificate expiry script
#!/bin/bash
# ssl-check.sh
DOMAIN="karangrejo.desa.id"
DAYS_WARNING=30
# Get certificate expiry date
EXPIRY_DATE=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | \
openssl x509 -noout -enddate | cut -d= -f2)
# Convert to epoch time
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
CURRENT_EPOCH=$(date +%s)
# Calculate days until expiry
DAYS_UNTIL_EXPIRY=$(( (EXPIRY_EPOCH - CURRENT_EPOCH) / 86400 ))
if [ $DAYS_UNTIL_EXPIRY -le $DAYS_WARNING ]; then
echo "⚠️ SSL certificate expires in $DAYS_UNTIL_EXPIRY days"
# Send alert email
echo "SSL certificate for $DOMAIN expires in $DAYS_UNTIL_EXPIRY days" | \
mail -s "SSL Certificate Expiry Warning" [email protected]
else
echo "✅ SSL certificate valid for $DAYS_UNTIL_EXPIRY days"
fi
// In AdminController
public function sslStatus()
{
$domain = config('app.url');
$parsed = parse_url($domain);
$hostname = $parsed['host'];
// Get SSL certificate info
$context = stream_context_create([
"ssl" => [
"capture_peer_cert" => true,
],
]);
$socket = stream_socket_client(
"ssl://{$hostname}:443",
$errno,
$errstr,
30,
STREAM_CLIENT_CONNECT,
$context
);
if ($socket) {
$cert = stream_context_get_params($socket)['options']['ssl']['peer_certificate'];
$certInfo = openssl_x509_parse($cert);
$sslData = [
'issuer' => $certInfo['issuer']['CN'] ?? 'Unknown',
'valid_from' => date('Y-m-d H:i:s', $certInfo['validFrom_time_t']),
'valid_to' => date('Y-m-d H:i:s', $certInfo['validTo_time_t']),
'days_until_expiry' => ceil(($certInfo['validTo_time_t'] - time()) / 86400),
'subject' => $certInfo['subject']['CN'] ?? 'Unknown',
];
fclose($socket);
} else {
$sslData = ['error' => "Unable to connect to {$hostname}:443"];
}
return view('admin.ssl.status', compact('sslData'));
}
SSL certificate adalah fondasi keamanan website modern 🔐