Skip to content

Operating System

CentOS

Firewall

bash
sudo firewall-cmd --permanent --add-port=9806/tcp
sudo firewall-cmd --zone=external --add-forward-port=port=22:proto=tcp:toport=3753
sudo firewall-cmd --reload

Fail2ban:

sh
sudo fail2ban-client status <segment>
sudo fail2ban-client unban <ip>

Check architecture

sh
uname -m
lscpu
cat /etc/os-release

SSH

RSA login

At the client:

sh
ssh-keygen -t rsa -b 4096

Copy the public key and paste it on the server.

At the server:

sh
vi ~/.ssh/authorized_keys

To disable password login for a specific user, edit /etc/ssh/sshd_config and add the line below:

txt
Match User <username> 
  PasswordAuthentication no

Then restart the sshd service.

Tunneling

  • -N: Do not execute a remote command. Useful when you only want to forward ports.
  • -f: Requests SSH to go to the background just before command execution.
  • -L: Local port forwarding.
sh
ssh -N -L 33306:localhost:3306 -i /path/to/key user@remote_host

docker-compose

bash
curl -L "https://github.com/docker/compose/releases/download/v2.13.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-linux-aarch64" -o /usr/local/bin/docker-compose

Note: It is recommended to use the official Docker Compose plugin instead.

Ubuntu / Debian

Create a sudo user

bash
sudo adduser deploy
sudo usermod -aG sudo deploy
id deploy

Verify sudo access:

bash
su - deploy
sudo whoami

Enable SSH private key login

On your local machine:

bash
ssh-keygen -t ed25519 -C "deploy@server"
ssh-copy-id deploy@<server-ip>

If ssh-copy-id is not available, copy the public key manually:

bash
mkdir -p ~/.ssh
chmod 700 ~/.ssh
vi ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Disable username and password login

Edit /etc/ssh/sshd_config:

txt
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes

Optional: restrict SSH to the deploy user only.

txt
AllowUsers deploy

Test the config before reloading SSH:

bash
sudo sshd -t
sudo systemctl reload ssh

CAUTION

Open a second SSH session and confirm key-based login works before closing your current session, otherwise you can lock yourself out.

Init script

Use this script to initialize Ubuntu.

bash
sudo apt update
sudo apt upgrade -y
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "/swapfile swap swap defaults 0 0" | sudo tee -a /etc/fstab
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo apt install btop fzf ncdu
sudo timedatectl set-timezone Asia/Kuala_Lumpur
sudo systemctl restart cron

Production-ready recommendations

After the base setup, harden the host with a few standard settings.

Install baseline packages:

bash
sudo apt-get install unattended-upgrades fail2ban ufw

Basic firewall:

bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

Enable automatic security updates:

bash
sudo dpkg-reconfigure -plow unattended-upgrades

Create an application user and app directory:

bash
sudo adduser deploy
sudo mkdir -p /srv/app
sudo chown -R deploy:deploy /srv/app

Recommended sysctl overrides for busy servers:

ini
# /etc/sysctl.d/99-custom.conf
fs.file-max = 65535
net.core.somaxconn = 1024
net.ipv4.tcp_fin_timeout = 15
vm.swappiness = 10

Apply:

bash
sudo sysctl --system

Swap file

Add a swap file:

bash
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "/swapfile swap swap defaults 0 0" | sudo tee -a /etc/fstab

Append to /etc/fstab

/swapfile swap swap defaults 0 0

Verify

bash
sudo swapon --show
sudo free -h

Extend a volume

sh
# List block devices
lsblk -f

# Check file system
df -T

# increase volume
sudo growpart /dev/xvda 1

# resize volume
sudo resize2fs /dev/xvda1

Show directory disk usage

sh
du -sh

Show total disk usage in a list

sh
ncdu

List the top 50 files or folders by size

sh
sudo du -h -a ./* | sort -hr | head -n 50

Delete journal logs by time

sh
sudo journalctl --vacuum-time=1month

Delete journal logs by size

sh
sudo journalctl --vacuum-size=1G

Check the journal

sh
sudo journalctl -xe
sudo journalctl -u <service name> -f

Truncate a file

sh
truncate -s 0 filename

Redirect stderr to stdout

bash
cat test.log 2>&1

Delete files older than 7 days

bash
find ~/path -type f -mtime +7 -delete

Backup script

bash
#!/bin/bash

# Define the backup directory
backup_dir=~/backup

# Create the backup directory if it doesn't exist
mkdir -p "$backup_dir"

# Read input line by line
while IFS= read -r item; do
  if [ -d "$item" ]; then
    # If item is a directory, create a corresponding directory in the backup location
    mkdir -p "$backup_dir/$item"
  elif [ -f "$item" ]; then
    # If item is a file, move it to the backup location, preserving the directory structure
    mkdir -p "$backup_dir/$(dirname "$item")"
    mv "$item" "$backup_dir/$item"
  else
    echo "Skipping $item: not a valid file or directory"
  fi
done

echo "Backup completed."

Misc

Time zone

To set the time zone:

sh
 sudo timedatectl set-timezone Asia/Kuala_Lumpur

Partition

List disks:

sh
fdisk -l

List blocks:

sh
lsblk

File system consistency check:

sh
fsck
e2fsck

Create a new partition from a disk:

  1. Run fdisk /dev/vdb.
  2. Press o to create a new empty DOS partition table.
  3. Press n to add a new partition.
  4. Press p to create a primary partition.
  5. Press w to write the table to disk and exit.
  6. Press Enter until the end.
  7. Run mkfs.ext4 /dev/vdb1 to format the partition.
  8. Run mount -t ext4 /dev/vdb1 /data to mount the partition to a path.

Mount automatically after restart:

  1. Run lsblk -f to get the UUID of the drive.
  2. Append UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX /mount/point filesystem type options 0 0 to /etc/fstab.
  3. Run sudo mount -a to check for errors.

Example: UUID=a95ff46e-55e7-445f-8513-37b2ad1b19ca /data ext4 defaults 0 0

Create a partition (Way 2):

bash
# Verify unused partition. Find the volume name
lsblk -f

# Create partition
sudo parted /dev/nvme0n1 --script mklabel gpt
sudo parted /dev/nvme0n1 --script mkpart primary ext4 0% 100%
sudo partprobe

# Verify
lsblk

# nvme0n1
# └─nvme0n1p1
# 

# Format partition
sudo mkfs.ext4 /dev/nvme0n1p1

# Mount partition
sudo mkdir -p /data
sudo mount /dev/nvme0n1p1 /data

# Verify
df -h | grep /data

# Auto mount
# Get uuid
sudo blkid /dev/nvme0n1p1
# UUID="1234abcd-5678-efgh"
# 

# Append to /etc/fstab
UUID=1234abcd-5678-efgh  /data  ext4  defaults,nofail  0  2

# Test, make sure no error
sudo mount -a

# Final sanity check
lsblk -f
# nvme0n1p1  ext4   ...   /data

Create a user group and user

Do not create the group before creating the user. The group will be created automatically.

sh
adduser username
passwd username
addgroup groupname

# for centos, use "wheel" instead of "sudo"
usermod -aG sudo username

Allow a user to use sudo without a password:

sh
sudo sh -c 'echo "$(logname) ALL=(ALL:ALL) NOPASSWD: ALL" > /etc/sudoers.d/$(logname)' && sudo chmod 440 /etc/sudoers.d/$(logname)

Install Docker (legacy)

sh
cat /etc/os-release
lscpu
uname -m
syscap info -arch

sudo apt install docker.io
sudo curl -L "https://github.com/docker/compose/releases/download/v2.21.0/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose

Install official Docker

sh
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

After configuring it, install Docker with the command below:

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Windows

Show Wi-Fi password

netsh wlan show profiles name=\<network name\> key=clear

Proxy

powershell
# Must run in admin mode
# add proxy
netsh interface portproxy add v4tov4 listenport=2222 listenaddress=0.0.0.0 connectport=22 connectaddress=127.0.0.1
# delete proxy
netsh interface portproxy delete v4tov4 listenport=2222 listenaddress=0.0.0.0

Bash

To execute a script that is not Bash, for example Node.js, you can use the header below:

bash
#!/usr/bin/env node

fzf

sh
git status -s | fzf --multi --preview 'git diff --color=always --unified=1000 -- {-1} '

tmux

Key binding

Ctrl + b

Window:

KeyDescription
cCreate window
,Rename window
&Close window
wList window
pPrevious window
nNext window
0 .. 9Switch to window 0 - 9

Pane:

KeyDescription
;Toggle last pane
%Horizontal split
"Vertical split
{Move pane left
}Move pane right
Up / Down / Left / RightSwitch to pane at the direction
oNext pane
qShow pane number
q 0 .. 9Switch to pane number x
Ctrl + Up / Down / Left / RightResize pane
xClose pane

Config

Basic config .tmux.conf

sh
set -g base-index 1
setw -g pane-base-index 1
setw -g mouse on

Advance config

sh
# Plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'catppuccin/tmux#v2.1.3' # See https://github.com/catppuccin/tmux/tags for additional tags

set -g default-terminal "screen-256color"
set -g base-index 1
setw -g pane-base-index 1
setw -g mouse on
# set -g window-status-format '#I:#W'
# set -g window-status-current-format '#[bold]#I:#W'
# setw -g mode-keys vi
# set -gF window-status-format "#[bg=#{@ctp_surface_1},fg=#{@ctp_fg}] ##I ##T "
# set -gF window-status-current-format "#[bg=#{@ctp_mauve},fg=#{@ctp_crust}] ##I ##T "

# Configure the catppuccin plugin
set -g @catppuccin_flavor "mocha"
set -g @catppuccin_window_status_style "rounded"
set -g @catppuccin_window_text " #W"
set -g @catppuccin_window_default_text "#W"
set -g @catppuccin_window_current_text " #W"
run ~/.tmux/plugins/tmux/catppuccin.tmux

# Make the status line pretty and add some modules
set -g status-right-length 100
set -g status-left-length 100
set -g status-left ""
set -g status-right "#{E:@catppuccin_status_application}"

#set -agF status-right "#{E:@catppuccin_status_cpu}"

set -ag status-right "#{E:@catppuccin_status_session}"
set -ag status-right "#{E:@catppuccin_status_uptime}"

#set -agF status-right "#{E:@catppuccin_status_battery}"

#Initialize TMUX plugin manager
run '~/.tmux/plugins/tpm/tpm'

If you face error 127 when sourcing the tmux config, it may be due to CRLF. Run this:

sh
find ~/.config/tmux/plugins -type f \( -name '*.tmux' -o -name '*.sh' -o -name '*.conf' \) -exec perl -pi -e 's/\r$//' {} +

Curl

sh
curl --ssl-no-revoke \
  -X POST 'https://url.com' \
  --header 'Accept: */*'   \
  --header 'Content-Type: application/json'   \
  --data-raw ''

Oh My Zsh

Themes

Custom DST:

bash
ZSH_THEME_GIT_PROMPT_PREFIX=" [%{$fg[green]%} "
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%}]"
ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[red]%}!"
ZSH_THEME_GIT_PROMPT_CLEAN=""

function prompt_char {
        if [ $UID -eq 0 ]; then echo "%{$fg[red]%}#%{$reset_color%}"; else echo $; fi
}

PROMPT='%(?, ,%{$fg[red]%}FAIL%{$reset_color%}
)
%{$fg[magenta]%}%n%{$reset_color%}@%{$fg[yellow]%}%m%{$reset_color%}: %{$fg_bold[blue]%}%~%{$reset_color%}$(git_prompt_info)
$(prompt_char) '

FTP / SFTP

Add a new user:

sh
sudo adduser --disabled-password sftpuser
sudo mkdir -p /home/sftpuser/.ssh
sudo touch /home/sftpuser/.ssh/authorized_keys
sudo chown -R sftpuser:sftpuser /home/sftpuser/.ssh
sudo chmod 700 /home/sftpuser/.ssh
sudo chmod 600 /home/sftpuser/.ssh/authorized_keys

Note: To allow ChrootDirectory and readonly access, the folder must be owned by root.

Production-ready SFTP setup

Create a chroot-safe directory layout:

sh
sudo adduser --disabled-password sftpuser
sudo mkdir -p /sftp/sftpuser/upload
sudo chown root:root /sftp
sudo chmod 755 /sftp
sudo chown root:root /sftp/sftpuser
sudo chmod 755 /sftp/sftpuser
sudo chown sftpuser:sftpuser /sftp/sftpuser/upload

Install the SSH public key:

sh
sudo mkdir -p /home/sftpuser/.ssh
sudo vi /home/sftpuser/.ssh/authorized_keys
sudo chown -R sftpuser:sftpuser /home/sftpuser/.ssh
sudo chmod 700 /home/sftpuser/.ssh
sudo chmod 600 /home/sftpuser/.ssh/authorized_keys

Change /etc/ssh/sshd_config:

sh
Match User sftpuser
    ForceCommand internal-sftp
    AllowUsers [email protected]
    PasswordAuthentication no
    ChrootDirectory /home/sftpuser
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no

Recommended hardened variant:

txt
Match User sftpuser
    ForceCommand internal-sftp
    PasswordAuthentication no
    PubkeyAuthentication yes
    ChrootDirectory /sftp/sftpuser
    PermitTunnel no
    AllowAgentForwarding no
    AllowTcpForwarding no
    X11Forwarding no

Validate and reload SSH:

sh
sudo sshd -t
sudo systemctl reload ssh

Log settings

Redirect logs

Add a new config file at /etc/rsyslog.d (ensure the .conf extension is used so rsyslog recognizes it):

sh
input(type="imuxsock" HostName="SOME-NAME" Socket="/some-path" CreatePath="on")
if $fromhost == 'SOME-NAME' then /destination-path
& stop

Log rotation

Amend rsyslog in /etc/logrotate.d to rotate the log file configured in rsyslog.

sh
/destination-path
{
  rotate 30
  daily
  dateext
  dateformat -%Y%m%d
}

Better settings:

sh
/destination-path
{
  rotate 30
  daily
  missingok
  notifempty
  compress
  delaycompress
  dateext
  dateformat -%Y%m%d
  postrotate
    systemctl restart rsyslog > /dev/null 2>&1 || true
  endscript
}
  • Ensure missingok and notifempty are enabled to avoid errors if the log is missing or empty.
  • Add compress and delaycompress for efficient storage.
  • Include postrotate to restart rsyslog after rotation, ensuring logs continue writing properly.

Give permission to rsyslog

Modify /etc/apparmor.d/usr.sbin.rsyslogd to grant read-write permissions to a directory that is not owned by rsyslog.

sh
/some-path rwl,
  • r = read
  • w = write
  • l = link