feat: implement SSL support with Let's Encrypt and update Nginx configuration
This commit is contained in:
Binary file not shown.
@@ -122,13 +122,23 @@ services:
|
|||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- ./certbot/www:/var/www/certbot:ro
|
||||||
|
- ./certbot/conf:/etc/letsencrypt:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- frontend
|
- frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# --- Certbot (Let's Encrypt) ---
|
||||||
|
certbot:
|
||||||
|
image: certbot/certbot
|
||||||
|
volumes:
|
||||||
|
- ./certbot/www:/var/www/certbot
|
||||||
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis_data:
|
redis_data:
|
||||||
upload_data:
|
upload_data:
|
||||||
|
|||||||
273
fixed-ssl.md
Normal file
273
fixed-ssl.md
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
سؤالك ممتاز—وده بالضبط الفرق بين **حل “يشتغل وخلاص”** وحل **Production حقيقي**.
|
||||||
|
|
||||||
|
خليني أشرح لك الأول **ليه الطريقة الأولى (certbot --nginx) مش مناسبة** عندك، وبعدين أديك **الحل الصحيح الجاهز (Nginx داخل Docker + SSL + Auto renew)** copy/paste.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# لماذا `certbot --nginx` مش مناسب عندك؟
|
||||||
|
|
||||||
|
أنت عندك:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Nginx شغال داخل Docker (saas-pdf-nginx-1)
|
||||||
|
```
|
||||||
|
|
||||||
|
لكن الأمر:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
certbot --nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
بيفترض إن:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Nginx مثبت على السيرفر (host)
|
||||||
|
```
|
||||||
|
|
||||||
|
📌 النتيجة:
|
||||||
|
|
||||||
|
* Certbot حاول يتحكم في Nginx الخارجي
|
||||||
|
* لكن البورت 80 مستخدم بالفعل بواسطة Docker
|
||||||
|
* حصل الخطأ: `Address already in use`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# هل الحل اللي عملناه “مؤقت”؟
|
||||||
|
|
||||||
|
لا 👇
|
||||||
|
|
||||||
|
## عندنا 3 مستويات حلول:
|
||||||
|
|
||||||
|
### 🥉 حل بدائي
|
||||||
|
|
||||||
|
تشغل Certbot خارج Docker → كل مرة توقف Nginx
|
||||||
|
❌ غير عملي
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🥈 حل متوسط
|
||||||
|
|
||||||
|
Certbot standalone + manual config
|
||||||
|
✔ شغال
|
||||||
|
❌ لكن renewal مش أوتوماتيك بسهولة
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🥇 الحل الاحترافي (اللي هنطبقه الآن)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Nginx + Certbot داخل Docker
|
||||||
|
```
|
||||||
|
|
||||||
|
✔ Auto renew
|
||||||
|
✔ scalable
|
||||||
|
✔ نفس طريقة الشركات الكبيرة
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# المشاكل لو ما عملتش الحل الصحيح
|
||||||
|
|
||||||
|
لو كملت بالحل القديم:
|
||||||
|
|
||||||
|
* ❌ SSL هينتهي كل 90 يوم
|
||||||
|
* ❌ الموقع هيقع فجأة
|
||||||
|
* ❌ Google هيقلل ترتيبك
|
||||||
|
* ❌ AdSense ممكن يرفضك
|
||||||
|
* ❌ صعب التوسعة لاحقًا
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# الآن الحل الاحترافي (جاهز للتطبيق)
|
||||||
|
|
||||||
|
هنعمل 3 حاجات:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. nginx config احترافي
|
||||||
|
2. docker-compose فيه SSL
|
||||||
|
3. auto renew
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1️⃣ تعديل nginx config
|
||||||
|
|
||||||
|
ادخل:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/SaaS-PDF/nginx
|
||||||
|
nano default.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
واستبدل كله بهذا:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name dociva.io www.dociva.io;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name dociva.io www.dociva.io;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/dociva.io/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/dociva.io/privkey.pem;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://frontend:5173;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2️⃣ تعديل docker-compose.yml
|
||||||
|
|
||||||
|
افتح:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano docker-compose.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
وابحث عن nginx وعدله:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./nginx:/etc/nginx/conf.d
|
||||||
|
- ./certbot/www:/var/www/certbot
|
||||||
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
|
depends_on:
|
||||||
|
- frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 3️⃣ إضافة certbot container
|
||||||
|
|
||||||
|
في نفس الملف أضف:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
certbot:
|
||||||
|
image: certbot/certbot
|
||||||
|
volumes:
|
||||||
|
- ./certbot/www:/var/www/certbot
|
||||||
|
- ./certbot/conf:/etc/letsencrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 4️⃣ تشغيل المشروع
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 5️⃣ إصدار الشهادة (مرة واحدة)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose run --rm certbot certonly \
|
||||||
|
--webroot \
|
||||||
|
--webroot-path=/var/www/certbot \
|
||||||
|
-d dociva.io -d www.dociva.io \
|
||||||
|
--email your@email.com \
|
||||||
|
--agree-tos --no-eff-email
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 6️⃣ إعادة تشغيل Nginx
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 7️⃣ اختبار
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://dociva.io
|
||||||
|
```
|
||||||
|
|
||||||
|
🔥 المفروض يشتغل
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 8️⃣ auto renew (مهم جداً)
|
||||||
|
|
||||||
|
أضف cron job:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
وأضف:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
0 3 * * * docker compose run --rm certbot renew && docker compose restart nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# النتيجة النهائية
|
||||||
|
|
||||||
|
✔ HTTPS شغال
|
||||||
|
✔ Auto renew
|
||||||
|
✔ Production ready
|
||||||
|
✔ مناسب لـ SEO + AdSense
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# خلاصة بسيطة
|
||||||
|
|
||||||
|
| الطريقة | هل مناسبة؟ |
|
||||||
|
| ---------------- | ---------- |
|
||||||
|
| certbot --nginx | ❌ لا |
|
||||||
|
| standalone | ⚠️ مؤقت |
|
||||||
|
| Docker SSL setup | ✅ الأفضل |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# رأيي كمستشار
|
||||||
|
|
||||||
|
أنت حالياً وصلت لمستوى:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Junior → Mid-level DevOps 🚀
|
||||||
|
```
|
||||||
|
|
||||||
|
ولو كملت بالشكل ده مشروعك فعلاً ممكن يتحول لمنتج حقيقي.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# الخطوة التالية بعد SSL
|
||||||
|
|
||||||
|
بعد ما نخلص:
|
||||||
|
|
||||||
|
1. Google Search Console
|
||||||
|
2. Sitemap
|
||||||
|
3. SEO pages
|
||||||
|
4. Analytics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
لو حابب، الخطوة الجاية أعملك:
|
||||||
|
|
||||||
|
👉 **إعداد SEO كامل يجلب أول 10,000 زيارة**
|
||||||
|
بطريقة عملية مش كلام نظري.
|
||||||
@@ -6,12 +6,35 @@ upstream frontend {
|
|||||||
server frontend:5173;
|
server frontend:5173;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── HTTP: ACME challenge + redirect to HTTPS ──
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name localhost 127.0.0.1 178.104.57.123 dociva.io www.dociva.io;
|
server_name dociva.io www.dociva.io;
|
||||||
|
|
||||||
|
location /.well-known/acme-challenge/ {
|
||||||
|
root /var/www/certbot;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── HTTPS ──
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name dociva.io www.dociva.io;
|
||||||
client_max_body_size 100M;
|
client_max_body_size 100M;
|
||||||
|
|
||||||
|
# SSL certificates (Let's Encrypt)
|
||||||
|
ssl_certificate /etc/letsencrypt/live/dociva.io/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/dociva.io/privkey.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
|
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-XSS-Protection "1; mode=block" always;
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
@@ -33,7 +56,7 @@ server {
|
|||||||
proxy_connect_timeout 60s;
|
proxy_connect_timeout 60s;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Frontend (Vite dev server in dev, static in prod)
|
# Frontend (Vite dev server)
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://frontend;
|
proxy_pass http://frontend;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
102
scripts/init-letsencrypt.sh
Normal file
102
scripts/init-letsencrypt.sh
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
# init-letsencrypt.sh
|
||||||
|
# Bootstrap Let's Encrypt certificates for dociva.io
|
||||||
|
#
|
||||||
|
# This script solves the chicken-and-egg problem:
|
||||||
|
# Nginx needs SSL certs to start on 443,
|
||||||
|
# but Certbot needs Nginx running on 80 to verify the domain.
|
||||||
|
#
|
||||||
|
# Solution: create a temporary self-signed cert → start Nginx →
|
||||||
|
# obtain the real cert → reload Nginx.
|
||||||
|
#
|
||||||
|
# Usage: chmod +x scripts/init-letsencrypt.sh
|
||||||
|
# sudo ./scripts/init-letsencrypt.sh
|
||||||
|
# ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
DOMAINS=(dociva.io www.dociva.io)
|
||||||
|
DATA_PATH="./certbot"
|
||||||
|
EMAIL="admin@dociva.io" # ← replace with your real email
|
||||||
|
RSA_KEY_SIZE=4096
|
||||||
|
STAGING=0 # Set to 1 to test against staging (no rate limits)
|
||||||
|
|
||||||
|
# ── colour helpers ──
|
||||||
|
info() { echo -e "\n\033[1;34m▶ $*\033[0m"; }
|
||||||
|
ok() { echo -e "\033[1;32m✔ $*\033[0m"; }
|
||||||
|
fail() { echo -e "\033[1;31m✖ $*\033[0m"; exit 1; }
|
||||||
|
|
||||||
|
# ── Pre-flight checks ──
|
||||||
|
command -v docker > /dev/null 2>&1 || fail "docker is not installed"
|
||||||
|
command -v docker compose > /dev/null 2>&1 && COMPOSE="docker compose" || COMPOSE="docker-compose"
|
||||||
|
|
||||||
|
# ── Step 1: Create required directories ──
|
||||||
|
info "Creating certbot directories …"
|
||||||
|
mkdir -p "$DATA_PATH/conf" "$DATA_PATH/www"
|
||||||
|
ok "Directories ready"
|
||||||
|
|
||||||
|
# ── Step 2: Download recommended TLS parameters ──
|
||||||
|
if [ ! -e "$DATA_PATH/conf/options-ssl-nginx.conf" ]; then
|
||||||
|
info "Downloading recommended TLS parameters …"
|
||||||
|
curl -sSf https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf \
|
||||||
|
-o "$DATA_PATH/conf/options-ssl-nginx.conf"
|
||||||
|
curl -sSf https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem \
|
||||||
|
-o "$DATA_PATH/conf/ssl-dhparams.pem"
|
||||||
|
ok "TLS parameters saved"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 3: Create temporary self-signed certificate ──
|
||||||
|
LIVE_DIR="$DATA_PATH/conf/live/dociva.io"
|
||||||
|
if [ ! -e "$LIVE_DIR/fullchain.pem" ]; then
|
||||||
|
info "Creating temporary self-signed certificate …"
|
||||||
|
mkdir -p "$LIVE_DIR"
|
||||||
|
openssl req -x509 -nodes -newkey rsa:2048 -days 1 \
|
||||||
|
-keyout "$LIVE_DIR/privkey.pem" \
|
||||||
|
-out "$LIVE_DIR/fullchain.pem" \
|
||||||
|
-subj "/CN=dociva.io" 2>/dev/null
|
||||||
|
ok "Temporary certificate created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Step 4: Start Nginx (uses the temp cert) ──
|
||||||
|
info "Starting Nginx …"
|
||||||
|
$COMPOSE up -d nginx
|
||||||
|
ok "Nginx is running"
|
||||||
|
|
||||||
|
# ── Step 5: Remove the temporary certificate ──
|
||||||
|
info "Removing temporary certificate …"
|
||||||
|
rm -rf "$LIVE_DIR"
|
||||||
|
ok "Temporary certificate removed"
|
||||||
|
|
||||||
|
# ── Step 6: Request real certificate from Let's Encrypt ──
|
||||||
|
info "Requesting Let's Encrypt certificate …"
|
||||||
|
|
||||||
|
DOMAIN_ARGS=""
|
||||||
|
for d in "${DOMAINS[@]}"; do
|
||||||
|
DOMAIN_ARGS="$DOMAIN_ARGS -d $d"
|
||||||
|
done
|
||||||
|
|
||||||
|
STAGING_ARG=""
|
||||||
|
if [ "$STAGING" -eq 1 ]; then
|
||||||
|
STAGING_ARG="--staging"
|
||||||
|
fi
|
||||||
|
|
||||||
|
$COMPOSE run --rm certbot certonly --webroot \
|
||||||
|
--webroot-path=/var/www/certbot \
|
||||||
|
$DOMAIN_ARGS \
|
||||||
|
--email "$EMAIL" \
|
||||||
|
--agree-tos \
|
||||||
|
--no-eff-email \
|
||||||
|
--force-renewal \
|
||||||
|
$STAGING_ARG
|
||||||
|
|
||||||
|
ok "Certificate obtained successfully"
|
||||||
|
|
||||||
|
# ── Step 7: Reload Nginx with the real certificate ──
|
||||||
|
info "Reloading Nginx …"
|
||||||
|
$COMPOSE exec nginx nginx -s reload
|
||||||
|
ok "Nginx reloaded with Let's Encrypt certificate"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
ok "HTTPS is now active for ${DOMAINS[*]}"
|
||||||
|
echo " Test: curl -I https://dociva.io"
|
||||||
Reference in New Issue
Block a user