/.env
/.env.production
/backup/.env
/wp/.env
/magento/.env
/api/v2/.env
/gateway/.env
/vendor/.env
/storage/.env
/.git/config
/credentials.json
/service-account.json
/__env.js
/actuator/env
/admin/phpinfo.php
/wp-admin/install.php
/.env
/.env.production
/backup/.env
/wp/.env
/magento/.env
/api/v2/.env
/gateway/.env
/vendor/.env
/storage/.env
/.git/config
/credentials.json
/service-account.json
/__env.js
/actuator/env
/admin/phpinfo.php
/wp-admin/install.php
/.env
/.env.production
/backup/.env
/wp/.env
/magento/.env
/api/v2/.env
/gateway/.env
/vendor/.env
/storage/.env
/.git/config
/credentials.json
/service-account.json
/__env.js
/actuator/env
/admin/phpinfo.php
/wp-admin/install.php
GET /.env
GET /.env.production
GET /backup/.env
GET /wp/.env
GET /storage/.env
GET /credentials.json
GET /service-account.json
GET /.git/config
GET /.env
GET /.env.production
GET /backup/.env
GET /wp/.env
GET /storage/.env
GET /credentials.json
GET /service-account.json
GET /.git/config
GET /.env
GET /.env.production
GET /backup/.env
GET /wp/.env
GET /storage/.env
GET /credentials.json
GET /service-account.json
GET /.git/config
adduser deploy
usermod -aG sudo deploy
adduser deploy
usermod -aG sudo deploy
adduser deploy
usermod -aG sudo deploy
mkdir -p /home/deploy/.ssh
nano /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
mkdir -p /home/deploy/.ssh
nano /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
mkdir -p /home/deploy/.ssh
nano /home/deploy/.ssh/authorized_keys chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys
ssh deploy@SERVER_IP
ssh deploy@SERVER_IP
ssh deploy@SERVER_IP
sudo nano /etc/ssh/sshd_config
sudo nano /etc/ssh/sshd_config
sudo nano /etc/ssh/sshd_config
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
X11Forwarding no
AllowUsers deploy
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
X11Forwarding no
AllowUsers deploy
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
X11Forwarding no
AllowUsers deploy
sudo sshd -t
sudo sshd -t
sudo sshd -t
sudo systemctl reload ssh
sudo systemctl reload ssh
sudo systemctl reload ssh
ssh -p 2222 deploy@SERVER_IP
ssh -p 2222 deploy@SERVER_IP
ssh -p 2222 deploy@SERVER_IP
root login disabled
password login disabled
only specific users allowed
key-based authentication required
root login disabled
password login disabled
only specific users allowed
key-based authentication required
root login disabled
password login disabled
only specific users allowed
key-based authentication required
sudo ufw default deny incoming
sudo ufw default allow outgoing sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw default deny incoming
sudo ufw default allow outgoing sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw default deny incoming
sudo ufw default allow outgoing sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
sudo ufw enable
sudo ufw status verbose
sudo ufw enable
sudo ufw status verbose
sudo ufw delete allow 2222/tcp
sudo ufw allow from YOUR_PUBLIC_IP to any port 2222 proto tcp
sudo ufw delete allow 2222/tcp
sudo ufw allow from YOUR_PUBLIC_IP to any port 2222 proto tcp
sudo ufw delete allow 2222/tcp
sudo ufw allow from YOUR_PUBLIC_IP to any port 2222 proto tcp
sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
sudo apt update
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
sudo nano /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
backend = systemd
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
backend = systemd
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
findtime = 10m
bantime = 1h
backend = systemd
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
/.env
/.env.production
/.git/config
/credentials.json
/service-account.json
/actuator/env
/admin/phpinfo.php
/.env
/.env.production
/.git/config
/credentials.json
/service-account.json
/actuator/env
/admin/phpinfo.php
/.env
/.env.production
/.git/config
/credentials.json
/service-account.json
/actuator/env
/admin/phpinfo.php
sudo nano /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
sudo nano /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
sudo nano /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD) /(.*)?(\.env|\.git/config|credentials\.json|service-account\.json|__env\.js|actuator/env|phpinfo\.php|wp-admin/install\.php).*" (403|404|444) .*
ignoreregex =
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD) /(.*)?(\.env|\.git/config|credentials\.json|service-account\.json|__env\.js|actuator/env|phpinfo\.php|wp-admin/install\.php).*" (403|404|444) .*
ignoreregex =
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD) /(.*)?(\.env|\.git/config|credentials\.json|service-account\.json|__env\.js|actuator/env|phpinfo\.php|wp-admin/install\.php).*" (403|404|444) .*
ignoreregex =
[nginx-sensitive-paths]
enabled = true
port = http,https
filter = nginx-sensitive-paths
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 10m
bantime = 6h
[nginx-sensitive-paths]
enabled = true
port = http,https
filter = nginx-sensitive-paths
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 10m
bantime = 6h
[nginx-sensitive-paths]
enabled = true
port = http,https
filter = nginx-sensitive-paths
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 10m
bantime = 6h
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-paths.conf
# Block hidden files such as .env, .git, .htaccess
location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off;
} # Block common secret and config file names
location ~* ^/(.*)?(\.env|\.env\..*|credentials\.json|service-account\.json|__env\.js|composer\.(json|lock)|package-lock\.json|yarn\.lock)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block backup/archive/database dump files
location ~* \.(bak|backup|old|orig|save|swp|sql|sqlite|db|tar|gz|zip|7z|rar)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block obvious PHP probing on non-PHP apps
location ~* /(phpinfo\.php|wp-admin/install\.php|xmlrpc\.php)$ { return 404;
}
# Block hidden files such as .env, .git, .htaccess
location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off;
} # Block common secret and config file names
location ~* ^/(.*)?(\.env|\.env\..*|credentials\.json|service-account\.json|__env\.js|composer\.(json|lock)|package-lock\.json|yarn\.lock)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block backup/archive/database dump files
location ~* \.(bak|backup|old|orig|save|swp|sql|sqlite|db|tar|gz|zip|7z|rar)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block obvious PHP probing on non-PHP apps
location ~* /(phpinfo\.php|wp-admin/install\.php|xmlrpc\.php)$ { return 404;
}
# Block hidden files such as .env, .git, .htaccess
location ~ /\.(?!well-known) { deny all; access_log off; log_not_found off;
} # Block common secret and config file names
location ~* ^/(.*)?(\.env|\.env\..*|credentials\.json|service-account\.json|__env\.js|composer\.(json|lock)|package-lock\.json|yarn\.lock)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block backup/archive/database dump files
location ~* \.(bak|backup|old|orig|save|swp|sql|sqlite|db|tar|gz|zip|7z|rar)$ { deny all; access_log /var/log/nginx/security-access.log;
} # Block obvious PHP probing on non-PHP apps
location ~* /(phpinfo\.php|wp-admin/install\.php|xmlrpc\.php)$ { return 404;
}
location /admin/ { allow YOUR_TRUSTED_IP; deny all; proxy_pass http://127.0.0.1:3050; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
}
location /admin/ { allow YOUR_TRUSTED_IP; deny all; proxy_pass http://127.0.0.1:3050; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
}
location /admin/ { allow YOUR_TRUSTED_IP; deny all; proxy_pass http://127.0.0.1:3050; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;
}
real_ip_header CF-Connecting-IP;
set_real_ip_from CLOUDFLARE_IP_RANGE;
real_ip_header CF-Connecting-IP;
set_real_ip_from CLOUDFLARE_IP_RANGE;
real_ip_header CF-Connecting-IP;
set_real_ip_from CLOUDFLARE_IP_RANGE;
DATABASE_URL
REDIS_URL
JWT_SECRET
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
DIGITALOCEAN_SPACES_KEY
STRIPE_SECRET_KEY
SMTP_PASSWORD
TELEGRAM_BOT_TOKEN
GOOGLE_SERVICE_ACCOUNT_JSON
SENTRY_DSN
DATABASE_URL
REDIS_URL
JWT_SECRET
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
DIGITALOCEAN_SPACES_KEY
STRIPE_SECRET_KEY
SMTP_PASSWORD
TELEGRAM_BOT_TOKEN
GOOGLE_SERVICE_ACCOUNT_JSON
SENTRY_DSN
DATABASE_URL
REDIS_URL
JWT_SECRET
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
DIGITALOCEAN_SPACES_KEY
STRIPE_SECRET_KEY
SMTP_PASSWORD
TELEGRAM_BOT_TOKEN
GOOGLE_SERVICE_ACCOUNT_JSON
SENTRY_DSN
/var/www/app/public/.env
/var/www/html/.env
/usr/share/nginx/html/.env
/var/www/app/public/.env
/var/www/html/.env
/usr/share/nginx/html/.env
/var/www/app/public/.env
/var/www/html/.env
/usr/share/nginx/html/.env
/opt/myapp/.env
/etc/myapp/myapp.env
/home/deploy/apps/myapp/.env
/opt/myapp/.env
/etc/myapp/myapp.env
/home/deploy/apps/myapp/.env
/opt/myapp/.env
/etc/myapp/myapp.env
/home/deploy/apps/myapp/.env
sudo chown deploy:deploy /opt/myapp/.env
sudo chmod 600 /opt/myapp/.env
sudo chown deploy:deploy /opt/myapp/.env
sudo chmod 600 /opt/myapp/.env
sudo chown deploy:deploy /opt/myapp/.env
sudo chmod 600 /opt/myapp/.env
[Service]
User=deploy
Group=deploy
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
[Service]
User=deploy
Group=deploy
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
[Service]
User=deploy
Group=deploy
EnvironmentFile=/etc/myapp/myapp.env
ExecStart=/usr/bin/node /opt/myapp/server.js
sudo chown root:deploy /etc/myapp/myapp.env
sudo chmod 640 /etc/myapp/myapp.env
sudo chown root:deploy /etc/myapp/myapp.env
sudo chmod 640 /etc/myapp/myapp.env
sudo chown root:deploy /etc/myapp/myapp.env
sudo chmod 640 /etc/myapp/myapp.env
.env
.env.*
!.env.example
.env
.env.*
!.env.example
.env
.env.*
!.env.example
DATABASE_URL=postgres://user:password@localhost:5432/app
JWT_SECRET=change-me
TELEGRAM_BOT_TOKEN=change-me
DATABASE_URL=postgres://user:password@localhost:5432/app
JWT_SECRET=change-me
TELEGRAM_BOT_TOKEN=change-me
DATABASE_URL=postgres://user:password@localhost:5432/app
JWT_SECRET=change-me
TELEGRAM_BOT_TOKEN=change-me
ENV DATABASE_URL=postgres://real-secret
COPY .env /app/.env
ENV DATABASE_URL=postgres://real-secret
COPY .env /app/.env
ENV DATABASE_URL=postgres://real-secret
COPY .env /app/.env
services: api: image: my-api:latest env_file: - /etc/myapp/myapp.env
services: api: image: my-api:latest env_file: - /etc/myapp/myapp.env
services: api: image: my-api:latest env_file: - /etc/myapp/myapp.env
database passwords
API keys
cloud access keys
JWT secrets
SMTP credentials
bot tokens
webhook secrets
object storage keys
third-party service tokens
database passwords
API keys
cloud access keys
JWT secrets
SMTP credentials
bot tokens
webhook secrets
object storage keys
third-party service tokens
database passwords
API keys
cloud access keys
JWT secrets
SMTP credentials
bot tokens
webhook secrets
object storage keys
third-party service tokens
FROM node:22-alpine WORKDIR /app COPY package*.json ./
RUN npm ci --omit=dev COPY . . RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser CMD ["node", "server.js"]
FROM node:22-alpine WORKDIR /app COPY package*.json ./
RUN npm ci --omit=dev COPY . . RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser CMD ["node", "server.js"]
FROM node:22-alpine WORKDIR /app COPY package*.json ./
RUN npm ci --omit=dev COPY . . RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser CMD ["node", "server.js"]
services: api: build: . user: "10001:10001" read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true tmpfs: - /tmp ports: - "127.0.0.1:3000:3000"
services: api: build: . user: "10001:10001" read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true tmpfs: - /tmp ports: - "127.0.0.1:3000:3000"
services: api: build: . user: "10001:10001" read_only: true cap_drop: - ALL security_opt: - no-new-privileges:true tmpfs: - /tmp ports: - "127.0.0.1:3000:3000"
do not mount / unnecessarily
do not mount docker.sock into random containers
drop Linux capabilities when possible
use read-only filesystems where possible
bind services to 127.0.0.1 behind Nginx
avoid privileged: true unless there is a very strong reason
do not mount / unnecessarily
do not mount docker.sock into random containers
drop Linux capabilities when possible
use read-only filesystems where possible
bind services to 127.0.0.1 behind Nginx
avoid privileged: true unless there is a very strong reason
do not mount / unnecessarily
do not mount docker.sock into random containers
drop Linux capabilities when possible
use read-only filesystems where possible
bind services to 127.0.0.1 behind Nginx
avoid privileged: true unless there is a very strong reason
[Service]
User=deploy
Group=deploy
Restart=always
RestartSec=5 NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true MemoryMax=500M
CPUQuota=80%
TasksMax=200
LimitNOFILE=65535
[Service]
User=deploy
Group=deploy
Restart=always
RestartSec=5 NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true MemoryMax=500M
CPUQuota=80%
TasksMax=200
LimitNOFILE=65535
[Service]
User=deploy
Group=deploy
Restart=always
RestartSec=5 NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true MemoryMax=500M
CPUQuota=80%
TasksMax=200
LimitNOFILE=65535
services: api: deploy: resources: limits: cpus: "1.0" memory: 512M
services: api: deploy: resources: limits: cpus: "1.0" memory: 512M
services: api: deploy: resources: limits: cpus: "1.0" memory: 512M
top
htop
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head
systemctl status SERVICE_NAME
journalctl -u SERVICE_NAME -f
top
htop
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head
systemctl status SERVICE_NAME
journalctl -u SERVICE_NAME -f
top
htop
ps aux --sort=-%cpu | head
ps aux --sort=-%mem | head
systemctl status SERVICE_NAME
journalctl -u SERVICE_NAME -f
ss -tunap
sudo lsof -i -P -n
ss -tunap
sudo lsof -i -P -n
ss -tunap
sudo lsof -i -P -n
uptime
top
ps aux --sort=-%cpu | head -30
ps aux --sort=-%mem | head -30
uptime
top
ps aux --sort=-%cpu | head -30
ps aux --sort=-%mem | head -30
uptime
top
ps aux --sort=-%cpu | head -30
ps aux --sort=-%mem | head -30
readlink -f /proc/PID/exe
tr '\0' ' ' < /proc/PID/cmdline
ls -la /proc/PID/fd
cat /proc/PID/environ 2>/dev/null | tr '\0' '\n'
readlink -f /proc/PID/exe
tr '\0' ' ' < /proc/PID/cmdline
ls -la /proc/PID/fd
cat /proc/PID/environ 2>/dev/null | tr '\0' '\n'
readlink -f /proc/PID/exe
tr '\0' ' ' < /proc/PID/cmdline
ls -la /proc/PID/fd
cat /proc/PID/environ 2>/dev/null | tr '\0' '\n'
ss -tunap
sudo lsof -i -P -n
ss -tunap
sudo lsof -i -P -n
ss -tunap
sudo lsof -i -P -n
sudo find /tmp /var/tmp /dev/shm -type f -mtime -2 -ls 2>/dev/null
sudo find /etc/systemd /etc/cron* /var/spool/cron -type f -mtime -7 -ls 2>/dev/null
sudo find /tmp /var/tmp /dev/shm -type f -mtime -2 -ls 2>/dev/null
sudo find /etc/systemd /etc/cron* /var/spool/cron -type f -mtime -7 -ls 2>/dev/null
sudo find /tmp /var/tmp /dev/shm -type f -mtime -2 -ls 2>/dev/null
sudo find /etc/systemd /etc/cron* /var/spool/cron -type f -mtime -7 -ls 2>/dev/null
crontab -l
sudo ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily
systemctl list-timers
systemctl list-units --type=service --state=running
crontab -l
sudo ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily
systemctl list-timers
systemctl list-units --type=service --state=running
crontab -l
sudo ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily
systemctl list-timers
systemctl list-units --type=service --state=running
sudo journalctl --since "24 hours ago"
sudo grep -i "failed password" /var/log/auth.log
sudo grep -i "accepted" /var/log/auth.log
sudo journalctl --since "24 hours ago"
sudo grep -i "failed password" /var/log/auth.log
sudo grep -i "accepted" /var/log/auth.log
sudo journalctl --since "24 hours ago"
sudo grep -i "failed password" /var/log/auth.log
sudo grep -i "accepted" /var/log/auth.log
high CPU with unknown binary
process running from /tmp, /var/tmp, or /dev/shm
weird random process names
outbound connections to unknown IPs
cron jobs that download shell scripts
systemd services with suspicious ExecStart
unexpected SSH keys added to authorized_keys
high CPU with unknown binary
process running from /tmp, /var/tmp, or /dev/shm
weird random process names
outbound connections to unknown IPs
cron jobs that download shell scripts
systemd services with suspicious ExecStart
unexpected SSH keys added to authorized_keys
high CPU with unknown binary
process running from /tmp, /var/tmp, or /dev/shm
weird random process names
outbound connections to unknown IPs
cron jobs that download shell scripts
systemd services with suspicious ExecStart
unexpected SSH keys added to authorized_keys
identify the process
identify how it started
identify persistence
identify network connections
identify modified files
rotate secrets
patch the entry point
rebuild if trust is lost
identify the process
identify how it started
identify persistence
identify network connections
identify modified files
rotate secrets
patch the entry point
rebuild if trust is lost
identify the process
identify how it started
identify persistence
identify network connections
identify modified files
rotate secrets
patch the entry point
rebuild if trust is lost
TLS termination
DDoS absorption
bot filtering
WAF managed rules
rate limiting
country/IP rules
header normalization
origin hiding
TLS termination
DDoS absorption
bot filtering
WAF managed rules
rate limiting
country/IP rules
header normalization
origin hiding
TLS termination
DDoS absorption
bot filtering
WAF managed rules
rate limiting
country/IP rules
header normalization
origin hiding
path traversal
SQL injection patterns
XSS probes
known CMS exploit paths
suspicious user agents
automated scanners
path traversal
SQL injection patterns
XSS probes
known CMS exploit paths
suspicious user agents
automated scanners
path traversal
SQL injection patterns
XSS probes
known CMS exploit paths
suspicious user agents
automated scanners
Internet -> CDN / WAF -> Nginx -> local app on 127.0.0.1 -> private database/cache
Internet -> CDN / WAF -> Nginx -> local app on 127.0.0.1 -> private database/cache
Internet -> CDN / WAF -> Nginx -> local app on 127.0.0.1 -> private database/cache
Internet -> Node.js app directly -> database accidentally exposed
Internet -> Node.js app directly -> database accidentally exposed
Internet -> Node.js app directly -> database accidentally exposed
server_tokens off;
server_tokens off;
server_tokens off;
client_max_body_size 10m;
client_max_body_size 10m;
client_max_body_size 10m;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://127.0.0.1:3000; }
}
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://127.0.0.1:3000; }
}
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; proxy_pass http://127.0.0.1:3000; }
}
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location ~* ^/(internal|private|backup|storage|vendor)/ { deny all;
}
location ~* ^/(internal|private|backup|storage|vendor)/ { deny all;
}
location ~* ^/(internal|private|backup|storage|vendor)/ { deny all;
}
return 444;
return 444;
return 444;
tail Nginx access logs
detect new client IPs
detect request bursts
detect sensitive path scans
watch CPU/RAM pressure
inspect suspicious processes
send compact Telegram alerts
tail Nginx access logs
detect new client IPs
detect request bursts
detect sensitive path scans
watch CPU/RAM pressure
inspect suspicious processes
send compact Telegram alerts
tail Nginx access logs
detect new client IPs
detect request bursts
detect sensitive path scans
watch CPU/RAM pressure
inspect suspicious processes
send compact Telegram alerts
SENSITIVE_PATH_SCAN
path=/.env
status=404
SENSITIVE_PATH_SCAN
path=/.env
status=404
SENSITIVE_PATH_SCAN
path=/.env
status=404
NEW_IP
path=/credentials.json
status=404
NEW_IP
path=/credentials.json
status=404
NEW_IP
path=/credentials.json
status=404
NEW_IP
path=/actuator/env
status=404
NEW_IP
path=/actuator/env
status=404
NEW_IP
path=/actuator/env
status=404
Nginx deny rules
Fail2Ban filters
WAF rules
alert categories
incident review notes
Nginx deny rules
Fail2Ban filters
WAF rules
alert categories
incident review notes
Nginx deny rules
Fail2Ban filters
WAF rules
alert categories
incident review notes
observe -> classify -> block -> monitor -> tune
observe -> classify -> block -> monitor -> tune
observe -> classify -> block -> monitor -> tune
Create non-root sudo user
Install SSH key
Disable root SSH login
Disable password SSH login
Move SSH to a non-default port
Allow SSH only from trusted IPs if possible
Enable UFW with default deny incoming
Allow only required ports
Install and configure Fail2Ban
Add custom Nginx filters for sensitive path scans
Block hidden files and secret files in Nginx
Keep .env outside public web roots
Set .env permissions to 600 or 640
Never commit .env
Never bake secrets into Docker images
Run app containers as non-root
Drop Docker capabilities
Avoid privileged containers
Bind internal services to 127.0.0.1
Put production apps behind CDN/WAF
Restrict origin access where possible
Monitor CPU/RAM/process/network behavior
Check cron/systemd persistence during incidents
Rotate secrets after any suspected exposure
Rebuild compromised servers when trust is lost
Create non-root sudo user
Install SSH key
Disable root SSH login
Disable password SSH login
Move SSH to a non-default port
Allow SSH only from trusted IPs if possible
Enable UFW with default deny incoming
Allow only required ports
Install and configure Fail2Ban
Add custom Nginx filters for sensitive path scans
Block hidden files and secret files in Nginx
Keep .env outside public web roots
Set .env permissions to 600 or 640
Never commit .env
Never bake secrets into Docker images
Run app containers as non-root
Drop Docker capabilities
Avoid privileged containers
Bind internal services to 127.0.0.1
Put production apps behind CDN/WAF
Restrict origin access where possible
Monitor CPU/RAM/process/network behavior
Check cron/systemd persistence during incidents
Rotate secrets after any suspected exposure
Rebuild compromised servers when trust is lost
Create non-root sudo user
Install SSH key
Disable root SSH login
Disable password SSH login
Move SSH to a non-default port
Allow SSH only from trusted IPs if possible
Enable UFW with default deny incoming
Allow only required ports
Install and configure Fail2Ban
Add custom Nginx filters for sensitive path scans
Block hidden files and secret files in Nginx
Keep .env outside public web roots
Set .env permissions to 600 or 640
Never commit .env
Never bake secrets into Docker images
Run app containers as non-root
Drop Docker capabilities
Avoid privileged containers
Bind internal services to 127.0.0.1
Put production apps behind CDN/WAF
Restrict origin access where possible
Monitor CPU/RAM/process/network behavior
Check cron/systemd persistence during incidents
Rotate secrets after any suspected exposure
Rebuild compromised servers when trust is lost - firewall first
- SSH exposure reduction
- key-only authentication
- non-root users
- Fail2Ban for behavior-based blocking
- Nginx deny rules and allow lists
- Docker isolation
- process and resource monitoring
- CDN and WAF in front
- forensic habits when something looks wrong
- secret isolation, especially around .env - Ubuntu UFW documentation: https://ubuntu.com/server/docs/how-to/security/firewalls/
- Nginx access module: https://nginx.org/en/docs/http/ngx_http_access_module.html
- Nginx documentation: https://nginx.org/en/docs/
- Fail2Ban filters documentation: https://fail2ban.readthedocs.io/en/latest/filters.html
- Docker rootless mode: https://docs.docker.com/engine/security/rootless/ - Joined Mar 10, 2026 - Location Yerevan
- Education Master in Computer science
- Work Software engineer, System Architect
- Joined Apr 26, 2019