신규 입사 3일차, 팀장이 슬랙으로 메시지를 보냅니다. "스테이징 서버에 배포 권한 줬는데 접속이 안 된다고 하던데?" 새로 합류한 백엔드 개발자가 비밀번호 인증을 시도하다 fail2ban에 차단됐고, 정작 배포용 키는 아직 서버에 등록도 안 된 상태입니다. SSH 키 배포부터 다중 서버 접속 설정, 보안 하드닝까지 — 팀에 새 인원이 들어올 때마다 반복되는 이 과정을 한 번에 정리해 둘 필요가 있습니다.
SSH 고급 & 보안 하드닝
- 1공개키/개인키 비대칭키 암호화 원리와 SSH 인증 흐름을 설명할 수 있다
- 2ssh-keygen으로 ed25519 키 쌍을 생성하고 알고리즘을 선택할 수 있다
- 3ssh-copy-id로 공개키를 배포하고 authorized_keys 권한 문제를 진단할 수 있다
- 4~/.ssh/config로 ProxyJump와 다중 서버 접속을 설정할 수 있다
- 5sshd_config 보안 하드닝과 fail2ban으로 브루트포스 공격을 방어할 수 있다
ssh -Vssh-keygen -t ed25519 -C "user@example.com"chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keyssudo sshd -t비대칭키 암호화 원리 — 공개키와 개인키

"공개키를 서버에 등록한다"는 말을 들었는데, 공개키를 서버에 올려놓아도 안전한 이유가 뭔지 의문이 생깁니다. 또는 ssh-keygen으로 키 쌍을 만들었는데 id_rsa와 id_rsa.pub 중 어느 게 서버에 올라가야 하는지 헷갈린 적이 있을 것입니다. 비대칭키의 핵심은 공개키로 암호화한 것은 개인키로만 풀 수 있다는 것입니다. 공개키는 인터넷에 공개해도 안전하고, 개인키는 절대로 서버에 올리지 않습니다. SSH 접속 시 서버는 내 공개키로 암호화한 챌린지를 보내고, 나는 내 개인키로만 풀 수 있다는 것을 증명해서 인증합니다.
SSH 키 기반 인증의 핵심은 **비대칭키 암호화(Asymmetric Cryptography)**입니다. 대칭키 방식과 달리 암호화와 복호화에 서로 다른 키를 사용합니다.
핵심 개념
공개키는 서버에 등록해두는 자물쇠이고, 개인키는 내 손에만 있는 열쇠입니다. 두 키가 수학적으로 쌍을 이루기 때문에 개인키 없이는 공개키로 잠근 것을 열 수 없습니다.
| 구분 | 공개키 (Public Key) | 개인키 (Private Key) |
|---|---|---|
| 위치 | 서버의 ~/.ssh/authorized_keys | 클라이언트의 ~/.ssh/id_ed25519 |
| 공유 여부 | 누구에게나 공유 가능 | 절대 외부에 노출 금지 |
| 역할 | 암호화 / 신원 검증용 잠금장치 | 복호화 / 신원 증명용 열쇠 |
| 비유 | 자물쇠 | 열쇠 |
SSH 인증 흐름
클라이언트 서버
│ │
│─── SSH 접속 시도 ───────────────▶│
│ │ authorized_keys에서 공개키 조회
│◀── 챌린지(랜덤 데이터) 전송 ──────│
│ │
│ [개인키로 챌린지 서명] │
│─── 서명된 응답 전송 ────────────▶│
│ │ [공개키로 서명 검증]
│◀── 인증 성공, 세션 시작 ──────────│
비밀번호 방식은 비밀번호 자체가 네트워크를 통해 전송(암호화된 채널이라도)되는 반면, 키 기반 인증은 개인키가 클라이언트를 절대로 떠나지 않습니다. 서버는 챌린지에 대한 올바른 서명을 받았는지만 확인합니다.
주요 알고리즘 비교
알고리즘마다 키 길이와 보안 수준이 다릅니다. 새 키를 만들 때는 ed25519를 선택하고, 구형 서버와 연동해야 할 때만 RSA를 고려하세요.
| 알고리즘 | 키 길이 | 보안 수준 | 권장 여부 |
|---|---|---|---|
ed25519 | 256bit (고정) | 매우 높음 | 권장 |
rsa | 2048~4096bit | 높음 (4096 권장) | 레거시 호환용 |
ecdsa | 256~521bit | 높음 | 가능 |
dsa | 1024bit | 낮음 (broken) | 사용 금지 |
현대 환경에서는 ed25519를 기본으로 사용하세요. 타원곡선 기반으로 RSA 4096과 동등한 보안을 훨씬 짧은 키로 달성하며, 서명 속도도 빠릅니다.
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part4/exam_19/.ssh && cd /tmp/linux/part4/exam_19
# SSH 설정 샘플 파일 생성
cat > /tmp/linux/part4/exam_19/ssh_config_sample << 'EOF'
Host dev-server
HostName 192.168.1.10
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
Host bastion
HostName 10.0.0.1
User admin
IdentityFile ~/.ssh/bastion_key
Host prod-via-bastion
HostName 10.10.0.5
User app
ProxyJump bastion
EOF
이제 실습을 진행합니다.
키 쌍은 ssh-keygen 명령어로 생성합니다. 클라이언트(내 로컬 머신)에서 실행합니다.
ed25519 키 생성 (권장)
ssh-keygen -t ed25519 -C "work@mycompany.com"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alice/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase): ****
Enter same passphrase again: ****
Your identification has been saved in /home/alice/.ssh/id_ed25519
Your public key has been saved in /home/alice/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:abc123XYZqwerty789mnopABCDEF456uvwxyz01234 work@mycompany.com
The key's randomart image is:
+--[ED25519 256]--+
| .o+oo. |
| . o=oE |
| . +.=+ . |
| o *=o+ |
| . S+== |
| o.=+. |
| . oo+ |
| ..* |
| .o |
+----[SHA256]-----+
RSA 4096 키 생성 (레거시 시스템 호환용)
ssh-keygen -t rsa -b 4096 -C "legacy-server@mycompany.com" -f ~/.ssh/id_rsa_legacy
옵션 설명
ssh-keygen의 주요 옵션과 사용 시점입니다.
| 옵션 | 의미 |
|---|---|
-t ed25519 | 알고리즘 선택 |
-b 4096 | 키 비트 수 (RSA 전용) |
-C "comment" | 공개키에 추가될 주석 (이메일 권장) |
-f ~/.ssh/id_custom | 저장 경로 명시 (기본값 생략 가능) |
-N "" | 패스프레이즈 없이 생성 (자동화 스크립트용) |
passphrase 선택 기준
- 업무용 키: 반드시 패스프레이즈 설정 →
ssh-agent로 세션 중 캐시 - 자동화(CI/CD, cron): 패스프레이즈 없이 생성하고 파일 권한으로 보호
생성된 파일 확인
ls -la ~/.ssh/
total 24
drwx------. 2 alice alice 80 Mar 26 09:00 .
drwx------. 7 alice alice 200 Mar 26 09:00 ..
-rw-------. 1 alice alice 399 Mar 26 09:00 id_ed25519
-rw-r--r--. 1 alice alice 94 Mar 26 09:00 id_ed25519.pub
-rw-------. 1 alice alice 3243 Mar 25 14:23 id_rsa_legacy
-rw-r--r--. 1 alice alice 742 Mar 25 14:23 id_rsa_legacy.pub
공개키(.pub)는 r--r--r--(644), 개인키는 rw-------(600)이어야 합니다. 권한이 틀리면 SSH가 키를 거부합니다.
공개키 내용 확인
cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBk7qX2mN9pLvkRdWqZxMnOpQrStUvWxYz0123456789ab work@mycompany.com
- ls -la ~/.ssh/ 에서 id_ed25519는 600(-rw-------), id_ed25519.pub는 644(-rw-r--r--) 권한이다
- cat ~/.ssh/id_ed25519.pub 출력이 'ssh-ed25519 AAAA...' 형식으로 시작하면 정상적으로 생성된 것이다
- ssh-keygen -l -f ~/.ssh/id_ed25519.pub 로 키 지문(fingerprint)을 확인할 수 있다
- ~/.ssh/ 디렉토리 자체의 권한은 700(drwx------)이어야 하며, 그렇지 않으면 SSH가 키를 무시한다
생성한 공개키를 원격 서버의 ~/.ssh/authorized_keys에 등록해야 키 기반 인증을 사용할 수 있습니다.
방법 1: ssh-copy-id 사용 (권장)
# 기본 포트(22)인 경우
ssh-copy-id alice@192.168.10.50
# 포트가 다른 경우
ssh-copy-id -p 2222 alice@192.168.10.50
# 특정 키 파일 지정
ssh-copy-id -i ~/.ssh/id_rsa_legacy.pub alice@legacy-server.example.com
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/alice/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is your password
alice@192.168.10.50's password: ****
Number of key(s) added: 1
Now try logging into the machine, with: "ssh 'alice@192.168.10.50'"
and check to make sure that only the key(s) you wanted were added.
방법 2: 수동 복사 (ssh-copy-id가 없을 때)
# 한 줄로 공개키 추가
cat ~/.ssh/id_ed25519.pub | ssh alice@192.168.10.50 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
방법 3: 서버에서 직접 편집
# 서버에서 실행
mkdir -p ~/.ssh
chmod 700 ~/.ssh
vi ~/.ssh/authorized_keys
# 공개키 내용을 한 줄로 붙여넣기
chmod 600 ~/.ssh/authorized_keys
authorized_keys 구조와 옵션
# 기본 형태
ssh-ed25519 AAAAC3Nz...ab work@mycompany.com
# 특정 IP에서만 허용
from="192.168.10.0/24" ssh-ed25519 AAAAC3Nz...ab restricted-key
# 특정 명령어만 실행 가능한 키 (배포 자동화 등)
command="/usr/local/bin/deploy.sh",no-pty,no-x11-forwarding ssh-ed25519 AAAAC3Nz...ab deploy-only
# 포트 포워딩만 허용
no-agent-forwarding,no-pty,permitopen="localhost:5432" ssh-ed25519 AAAAC3Nz...ab db-tunnel-only
키 배포 후 검증
# 비밀번호 없이 접속되는지 확인
ssh -i ~/.ssh/id_ed25519 alice@192.168.10.50
# 접속 성공 시
Last login: Wed Mar 25 18:42:11 2026 from 192.168.1.5
[alice@web-server-01 ~]$
권한 요약표
SSH는 .ssh 디렉토리와 키 파일의 권한을 엄격하게 검사합니다. 아래 권한과 다르면 접속 자체를 거부합니다.
| 경로 | 올바른 권한 | 권한 숫자 |
|---|---|---|
~/.ssh/ | drwx------ | 700 |
~/.ssh/authorized_keys | -rw------- | 600 |
~/.ssh/id_ed25519 (개인키) | -rw------- | 600 |
~/.ssh/id_ed25519.pub (공개키) | -rw-r--r-- | 644 |
~/.ssh/config | -rw------- | 600 |
서버가 여러 대가 되면 ssh alice@192.168.10.50 -p 2222 -i ~/.ssh/id_rsa_special 같은 긴 명령을 매번 입력하기 번거롭습니다. ~/.ssh/config 파일에 서버별 설정을 정의하면 ssh web01처럼 짧게 접속할 수 있습니다.
기본 설정 파일 구조
# 파일 생성 또는 편집
vi ~/.ssh/config
chmod 600 ~/.ssh/config
# ~/.ssh/config
# 전역 기본값 (모든 Host에 적용)
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
# 개발 서버
Host dev
HostName 192.168.10.10
User alice
Port 22
IdentityFile ~/.ssh/id_ed25519
# 스테이징 서버 (비표준 포트)
Host staging
HostName staging.mycompany.com
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
# 프로덕션 DB 서버 (Bastion 경유)
Host prod-db
HostName 10.0.1.20
User dbadmin
Port 22
IdentityFile ~/.ssh/id_rsa_legacy
ProxyJump bastion
# Bastion 호스트 (점프 서버)
Host bastion
HostName bastion.mycompany.com
User alice
Port 22
IdentityFile ~/.ssh/id_ed25519
# 레거시 서버 (구버전 SSH 서버 호환)
Host legacy-server
HostName 10.0.2.100
User admin
Port 22
IdentityFile ~/.ssh/id_rsa_legacy
PubkeyAcceptedAlgorithms +ssh-rsa
HostKeyAlgorithms +ssh-rsa
# 와일드카드로 그룹 설정
Host *.internal.mycompany.com
User alice
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking accept-new
설정 후 사용
# 이전
ssh -p 2222 -i ~/.ssh/id_ed25519 deploy@staging.mycompany.com
# 이후
ssh staging
# scp, rsync도 동일한 별칭 사용 가능
scp ./app.tar.gz staging:/var/www/releases/
rsync -av ./dist/ prod-db:/backup/
주요 설정 키워드
자주 쓰는 ~/.ssh/config 키워드와 역할입니다.
| 키워드 | 설명 | 예시 |
|---|---|---|
Host | 별칭 (접속 시 사용) | Host web01 |
HostName | 실제 IP 또는 도메인 | HostName 10.0.1.5 |
User | 로그인 사용자명 | User ubuntu |
Port | SSH 포트 | Port 2222 |
IdentityFile | 사용할 개인키 경로 | IdentityFile ~/.ssh/id_prod |
ProxyJump | 경유할 점프 서버 | ProxyJump bastion |
ForwardAgent | ssh-agent 포워딩 | ForwardAgent yes |
ServerAliveInterval | keepalive 간격(초) | ServerAliveInterval 60 |
StrictHostKeyChecking | 호스트 키 검증 | StrictHostKeyChecking accept-new |
SSH 포트 포워딩 — 터널링의 세 가지 형태

개발 서버의 MySQL이 127.0.0.1에만 바인딩되어 있어서 로컬 DBeaver로 직접 접속이 안 됩니다. 보안상 bind-address를 0.0.0.0으로 열어두기는 꺼려지고, VPN 설정은 시간이 걸립니다. 또는 사무실 방화벽이 막혀 있어서 특정 외부 서비스에 접근하지 못하는데, 이미 열린 SSH 세션을 우회로로 쓰고 싶은 상황입니다. SSH 포트 포워딩은 이미 열린 SSH 터널 위에 다른 TCP 트래픽을 태워 보내는 방법으로, VPN 없이도 방화벽 안쪽 서비스에 안전하게 접근할 수 있습니다.
SSH 포트 포워딩은 SSH 암호화 채널을 통해 다른 TCP 트래픽을 안전하게 전송하는 기법입니다. 방화벽 내부 서비스에 외부에서 접근하거나, 내부 네트워크를 통해 외부 서비스에 연결할 때 사용합니다.
Local Forwarding (-L): 로컬 → 원격
로컬 포트를 원격 네트워크의 특정 주소:포트로 연결합니다. "내가 서버를 통해 원격 서비스에 접근"하는 패턴입니다.
클라이언트 SSH 서버 목적지
localhost:8080 ──암호화──▶ bastion.com ──평문──▶ db.internal:5432
Remote Forwarding (-R): 원격 → 로컬
원격 서버의 포트를 로컬 서비스로 연결합니다. "외부에서 내 로컬 서비스에 접근하게 허용"하는 패턴입니다. 내부망의 서비스를 일시적으로 외부에 노출할 때 유용합니다.
외부 사용자 SSH 서버 클라이언트(개발자)
browser ──────▶ server:9090 ──암호화──▶ localhost:3000 (개발서버)
Dynamic Forwarding (-D): SOCKS5 프록시
SOCKS5 프록시를 생성하여 브라우저나 애플리케이션의 모든 트래픽을 SSH 터널을 통해 라우팅합니다. VPN과 유사한 효과를 냅니다.
클라이언트 SSH 서버
브라우저 ──SOCKS5──▶ localhost:1080 ──암호화──▶ jump-server ──▶ 인터넷
세 가지 포워딩 방식의 플래그와 방향을 정리하면 어떤 상황에 무엇을 쓸지 바로 선택할 수 있습니다.
| 종류 | 플래그 | 방향 | 주요 사용 사례 |
|---|---|---|---|
| Local | -L | 클라이언트→원격 | DB 접근, 내부 웹UI 접근 |
| Remote | -R | 원격→클라이언트 | 개발 서버 외부 공유, webhook 수신 |
| Dynamic | -D | 클라이언트→인터넷 | 우회 접속, 내부망 전체 라우팅 |
Local Forwarding: 원격 DB에 로컬에서 접근
# 문법: ssh -L [로컬포트]:[목적지호스트]:[목적지포트] [SSH서버]
# 로컬 15432 → bastion → 내부 PostgreSQL 10.0.1.20:5432
ssh -L 15432:10.0.1.20:5432 alice@bastion.mycompany.com -N -f
# -N : 원격 명령 실행 없이 포워딩만 유지
# -f : 백그라운드로 실행
# 이제 로컬에서 내부 DB에 직접 접속 가능
psql -h localhost -p 15432 -U dbuser -d production
# 터널이 열렸는지 확인
ss -tlnp | grep 15432
LISTEN 0 128 127.0.0.1:15432 0.0.0.0:* users:(("ssh",pid=12345,fd=6))
~/.ssh/config에 포워딩 설정 영구화
매번 긴 -L 옵션을 입력하는 대신, ~/.ssh/config에 LocalForward를 정의하면 ssh -N -f 별칭 한 줄로 터널을 열 수 있습니다.
Host prod-db-tunnel
HostName bastion.mycompany.com
User alice
IdentityFile ~/.ssh/id_ed25519
LocalForward 15432 10.0.1.20:5432
LocalForward 16379 10.0.1.30:6379
ServerAliveInterval 30
# 이제 한 명령으로 모든 터널 오픈
ssh -N -f prod-db-tunnel
Remote Forwarding: 로컬 개발 서버를 외부에 노출
-R 옵션은 방향이 반대입니다. 원격 서버의 포트를 내 로컬 서비스로 연결해 외부에서 개발 서버에 접근하거나 webhook을 받을 수 있습니다.
# 문법: ssh -R [원격포트]:[로컬호스트]:[로컬포트] [SSH서버]
# 원격 서버의 9090 포트 → 내 로컬 개발서버 3000
ssh -R 9090:localhost:3000 alice@jump.mycompany.com -N -f
# 이제 외부에서 http://jump.mycompany.com:9090 으로 내 개발 서버 접근 가능
# (서버의 GatewayPorts yes 설정 필요할 수 있음)
Dynamic Forwarding: SOCKS5 프록시 생성
-D 옵션은 로컬에 SOCKS5 프록시를 만들고 모든 트래픽을 SSH 터널로 우회합니다. 특정 포트가 아닌 모든 연결을 SSH 서버를 통해 내보내고 싶을 때 씁니다.
# 문법: ssh -D [로컬포트] [SSH서버]
ssh -D 1080 alice@jump.mycompany.com -N -f
# curl에서 SOCKS5 프록시 사용
curl --socks5 localhost:1080 http://internal-service.company.local/
# 브라우저 설정: SOCKS5 프록시 → localhost:1080
터널 종료
-f 옵션으로 백그라운드에 올린 터널은 kill이나 Control Master로 종료합니다.
# 백그라운드 SSH 터널 프로세스 확인 및 종료
ps aux | grep 'ssh -[NfL]'
kill 12345
# 또는 Control Master로 깔끔하게 관리
ssh -O exit prod-db-tunnel
scp와 rsync over SSH
scp는 단순 복사, rsync는 변경된 부분만 전송하는 증분 동기화입니다. 소규모 파일은 scp, 디렉토리 배포나 대용량 파일은 rsync를 씁니다.
# scp: 단순 파일 복사
# 로컬 → 원격
scp ./config.yaml alice@dev:/etc/myapp/config.yaml
# 원격 → 로컬
scp alice@dev:/var/log/app.log ./logs/
# 디렉토리 복사 (-r)
scp -r ./dist/ alice@staging:/var/www/html/
# 비표준 포트
scp -P 2222 ./deploy.sh alice@prod-server:/tmp/
# rsync: 증분 동기화 (대용량 파일, 디렉토리 배포에 권장)
# -a: archive mode (권한, 타임스탬프, 심볼릭링크 유지)
# -v: verbose, -z: 압축, --delete: 원본에 없는 파일 삭제
rsync -avz --delete ./dist/ alice@staging:/var/www/html/
# ~/.ssh/config 별칭 사용
rsync -avz --progress ./app.tar.gz prod-db:/backup/releases/
# dry-run으로 먼저 확인
rsync -avzn --delete ./dist/ alice@staging:/var/www/html/
서버 측 SSH 데몬 설정 파일은 /etc/ssh/sshd_config입니다. 기본값은 보안보다 편의성 우선이므로 프로덕션 환경에서는 반드시 하드닝이 필요합니다.
현재 설정 백업 (필수)
설정을 수정하기 전에 반드시 백업을 남깁니다. 실수로 SSH 접속이 차단될 경우 원복할 수 있어야 합니다.
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)
핵심 보안 설정 적용
아래 설정을 sshd_config에 적용합니다. 항목별 주석을 읽고 서버 용도에 맞게 선택하세요.
sudo vi /etc/ssh/sshd_config
# /etc/ssh/sshd_config — 보안 하드닝 설정
# ── 포트 변경 ──────────────────────────────────────────
# 기본 22에서 비표준 포트로 변경 (자동 스캔 봇 회피)
# 변경 전 방화벽에 새 포트 오픈 필수!
Port 2222
# ── 인증 방식 제한 ──────────────────────────────────────
# 키 기반 인증 활성화
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# 비밀번호 인증 완전 차단 (키 배포 확인 후 설정)
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
# 빈 비밀번호 계정 로그인 금지
PermitEmptyPasswords no
# ── Root 로그인 제한 ────────────────────────────────────
# no: 완전 차단 (권장)
# prohibit-password: 키 인증만 허용
# forced-commands-only: 특정 명령어만 허용
PermitRootLogin no
# ── 접근 허용/차단 계정 ──────────────────────────────────
# 허용할 사용자 (공백으로 구분, 이 목록 외 모두 차단)
AllowUsers alice bob deploy
# 허용할 그룹 (AllowUsers 또는 AllowGroups 중 하나 사용)
# AllowGroups sshusers ops
# 명시적 차단 (AllowUsers보다 우선 적용 안 됨 — 중복 사용 주의)
DenyUsers oracle postgres
# ── 인증 시도 제한 ──────────────────────────────────────
# 인증 실패 최대 횟수 (초과 시 연결 끊김)
MaxAuthTries 3
# 동시 미완료 연결 수 제한
MaxStartups 10:30:60
# 인증 대기 시간 제한 (초)
LoginGraceTime 30
# ── 기타 보안 설정 ──────────────────────────────────────
# X11 포워딩 비활성화 (필요 없는 경우)
X11Forwarding no
# TCP keepalive 비활성화 (스푸핑 가능성)
TCPKeepAlive no
# SSH 프로토콜 버전 (현재 모든 배포판에서 2만 지원)
Protocol 2
# 허용할 암호화 알고리즘 (구버전 제거)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
# 세션 유지 (서버 측 keepalive)
ClientAliveInterval 300
ClientAliveCountMax 2
# SFTP 서브시스템 (필요 없으면 주석 처리)
Subsystem sftp /usr/lib/openssh/sftp-server
설정 문법 검증 (재시작 전 반드시 실행)
sshd -t는 실제로 데몬을 재시작하지 않고 설정 파일의 문법만 검사합니다. 오류가 있으면 재시작 없이 잡을 수 있습니다.
sudo sshd -t
# 에러가 없으면 아무 출력도 없음
# 에러가 있으면:
# /etc/ssh/sshd_config line 42: Bad configuration option: PasswordAuthenticaton
SSH 데몬 재시작
restart는 기존 세션을 끊지 않고 새 연결부터 새 설정을 적용합니다. 원격에서 작업 중이라면 새 터미널에서 새 설정으로 접속 되는지 확인한 후 기존 세션을 닫으세요.
# 현재 세션 유지하면서 재시작 (기존 연결은 끊기지 않음)
sudo systemctl restart sshd
# 또는
sudo systemctl reload sshd
# 재시작 후 상태 확인
sudo systemctl status sshd
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled)
Active: active (running) since Thu 2026-03-26 09:15:30 KST; 3s ago
Process: 18201 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
Main PID: 18202 (sshd)
포트 변경 후 방화벽 오픈 (반드시 재시작 전에 실행)
포트를 2222로 바꾼 경우, sshd를 재시작하기 전에 방화벽에 새 포트를 먼저 열어야 합니다. 순서가 바뀌면 서버에서 잠길 수 있습니다.
# firewalld 사용 환경 (RHEL/CentOS/Rocky)
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
# ufw 사용 환경 (Ubuntu/Debian)
sudo ufw allow 2222/tcp
sudo ufw status
# iptables 직접 제어 (구버전 환경)
sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
sudo service iptables save
SELinux 포트 레이블 추가 (RHEL 계열)
RHEL 계열에서는 SELinux가 활성화되어 있으면 방화벽 오픈만으로 부족합니다. 새 포트를 ssh_port_t 타입으로 레이블링해야 sshd가 해당 포트를 바인딩할 수 있습니다.
# SELinux를 사용하는 경우 새 포트를 ssh_port_t로 레이블 지정
sudo semanage port -a -t ssh_port_t -p tcp 2222
# 적용 확인
sudo semanage port -l | grep ssh
ssh_port_t tcp 2222, 22
fail2ban은 로그 파일을 모니터링하여 반복 실패한 IP를 자동으로 차단하는 침입 방지 도구입니다.
설치
fail2ban은 대부분의 배포판 기본 저장소에 없으므로, RHEL 계열은 EPEL을 먼저 추가합니다.
# RHEL/CentOS/Rocky Linux
sudo dnf install -y epel-release
sudo dnf install -y fail2ban
# Ubuntu/Debian
sudo apt install -y fail2ban
# 서비스 활성화
sudo systemctl enable --now fail2ban
기본 설정 구조
jail.conf는 직접 수정하지 않고 jail.local에 덮어쓸 설정만 작성합니다. 패키지 업그레이드로 jail.conf가 갱신돼도 jail.local은 유지됩니다.
# 기본 설정은 /etc/fail2ban/jail.conf
# 사용자 설정은 /etc/fail2ban/jail.local (jail.conf를 덮어씀)
sudo vi /etc/fail2ban/jail.local
[DEFAULT]
# 차단 지속 시간 (초 또는 시간:분:초)
bantime = 1h
# 이 시간 내에 maxretry번 실패하면 차단
findtime = 10m
# 최대 실패 횟수
maxretry = 5
# 차단에서 제외할 IP (절대로 차단되면 안 되는 IP)
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/24 203.0.113.10
# 이메일 알림 (sendmail 설정 필요)
# destemail = admin@mycompany.com
# action = %(action_mwl)s
[sshd]
enabled = true
port = 2222
# 포트를 변경한 경우 반드시 명시
filter = sshd
logpath = /var/log/secure
# Ubuntu/Debian의 경우: /var/log/auth.log
backend = systemd
maxretry = 3
bantime = 24h
findtime = 5m
재시작 및 상태 확인
설정 변경 후에는 재시작하고 서비스가 정상 구동됐는지 확인합니다.
sudo systemctl restart fail2ban
sudo systemctl status fail2ban
fail2ban 운영 명령어
fail2ban-client로 실행 중인 jail 상태를 조회하고 IP를 수동으로 차단하거나 해제할 수 있습니다.
# 전체 jail 상태 확인
sudo fail2ban-client status
Status
|- Number of jail: 1
`- Jail list: sshd
# SSH jail 상세 상태
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 47
| `- File list: /var/log/secure
`- Actions
|- Currently banned: 1
|- Total banned: 3
`- Banned IP list: 203.0.113.55
# 특정 IP 수동 차단
sudo fail2ban-client set sshd banip 203.0.113.55
# 특정 IP 차단 해제 (실수로 자신의 IP가 차단된 경우)
sudo fail2ban-client set sshd unbanip 203.0.113.55
# 차단된 IP 목록 확인
sudo fail2ban-client get sshd banned
# 로그 실시간 모니터링
sudo tail -f /var/log/fail2ban.log
2026-03-26 09:23:41,102 fail2ban.filter [sshd] Found 203.0.113.55 - 2026-03-26 09:23:41
2026-03-26 09:23:53,445 fail2ban.filter [sshd] Found 203.0.113.55 - 2026-03-26 09:23:53
2026-03-26 09:24:01,892 fail2ban.filter [sshd] Found 203.0.113.55 - 2026-03-26 09:24:01
2026-03-26 09:24:01,930 fail2ban.actions [sshd] Ban 203.0.113.55
차단 규칙 확인 (iptables/nftables)
fail2ban이 차단한 IP는 실제로 iptables 또는 nftables 규칙으로 등록됩니다. 차단이 실제 적용됐는지 방화벽 레벨에서 확인할 수 있습니다.
# iptables 기반 차단 규칙 확인
sudo iptables -L f2b-sshd -n --line-numbers
Chain f2b-sshd (1 references)
num target prot opt source destination
1 REJECT all -- 203.0.113.55 0.0.0.0/0 reject-with icmp-port-unreachable
2 RETURN all -- 0.0.0.0/0 0.0.0.0/0
상황: ssh-copy-id로 공개키를 배포했는데 여전히 비밀번호를 요구합니다.
원인: 서버의 .ssh 디렉토리나 authorized_keys 파일 권한이 너무 개방적이면 sshd가 보안상 해당 키를 무시합니다 (StrictModes yes 기본값).
진단
# -v 옵션으로 상세 디버그 로그 확인
ssh -v alice@192.168.10.50 2>&1 | grep -E 'Offering|denied|permission|Authentication'
debug1: Offering public key: /home/alice/.ssh/id_ed25519 ED25519 SHA256:abc123...
debug1: Authentications that can continue: publickey,password
debug1: Trying private key: /home/alice/.ssh/id_ed25519
debug1: Server accepts key: /home/alice/.ssh/id_ed25519...
Received disconnect from 192.168.10.50 port 22:2: Too many authentication failures
또는:
debug1: Authentication succeeded (publickey).
# 이게 없고 바로 password로 넘어가는 경우 → 키 자체가 거부됨
서버 측 로그 확인
# 서버에서 실행 (다른 방법으로 접속하거나 콘솔 접근)
sudo tail -50 /var/log/secure | grep sshd
# 또는
sudo journalctl -u sshd -n 50
Mar 26 09:30:15 server sshd[1234]: Authentication refused: bad ownership or modes for directory /home/alice/.ssh
Mar 26 09:30:15 server sshd[1234]: Connection closed by 192.168.1.5 [preauth]
해결
# 서버에서 실행
# 1. 홈 디렉토리 소유자 및 권한 확인
ls -la /home/ | grep alice
stat /home/alice
drwxrwxr-x. 5 alice alice 200 Mar 26 09:00 alice # ← 홈 디렉토리가 group/other에 쓰기 가능 — 문제!
# 2. 권한 수정
chmod 755 /home/alice # 홈 디렉토리: 755 또는 더 제한적으로
chmod 700 /home/alice/.ssh # .ssh 디렉토리: 700 (rwx------)
chmod 600 /home/alice/.ssh/authorized_keys # authorized_keys: 600 (rw-------)
# 3. 소유자 확인 (다른 사용자가 소유하면 안 됨)
chown alice:alice /home/alice/.ssh
chown alice:alice /home/alice/.ssh/authorized_keys
# 4. authorized_keys 내용 확인 (형식이 깨지지 않았는지)
cat /home/alice/.ssh/authorized_keys
# 한 줄에 하나의 키: "ssh-ed25519 AAAA... comment"
# 5. sshd 로그 재확인 후 재접속 테스트
ssh -v alice@192.168.10.50
StrictModes 설정 확인
/etc/ssh/sshd_config에서 StrictModes yes(기본값)가 설정되어 있으면 위 권한 검사를 수행합니다. 임시 디버깅용으로 no로 바꿔서 권한이 원인인지 확인할 수 있습니다(단, 임시 조치 후 반드시 yes로 복원).
상황: sshd_config에서 포트를 22 → 2222로 변경하고 재시작했는데 방화벽에 2222를 오픈하지 않아 서버 접근이 완전히 차단됐습니다. 프로덕션에서 발생하면 매우 위험한 상황입니다.
원인: 포트 변경 → 방화벽 오픈 → sshd 재시작 순서를 지키지 않아 새 포트가 방화벽 REJECT 상태에서 sshd가 bind됐습니다.
진단 및 예방 순서
포트 변경 순서:
1. 방화벽/보안그룹에 새 포트 오픈
2. SELinux 레이블 추가 (RHEL 계열)
3. sshd_config 수정
4. sshd -t 로 문법 검증
5. systemctl reload sshd
6. 새 포트로 접속 테스트 (기존 세션 유지한 채로!)
7. 기존 포트(22) 방화벽 닫기
해결 — Lock-out 상황별 복구
클라우드 환경 (AWS/GCP/Azure)
# AWS EC2의 경우:
# 1. EC2 Console → 인스턴스 선택 → Connect → EC2 Instance Connect (브라우저 SSH)
# 또는
# 2. Systems Manager → Session Manager (에이전트 설치된 경우)
# 또는
# 3. 인스턴스 중지 → 루트 볼륨 분리 → 다른 인스턴스에 연결 → 파일 수정 → 재연결
# 복구 후 실행:
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
# 또는 포트를 22로 되돌리기
sudo sed -i 's/^Port 2222/Port 22/' /etc/ssh/sshd_config
sudo systemctl restart sshd
온프레미스 / VPS (IPMI, KVM 콘솔 접근)
# 호스팅 업체의 KVM 콘솔 또는 IPMI/iDRAC/iLO로 직접 접속
# 콘솔에서 실행:
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
# 또는 포트를 22로 복구
sudo cp /etc/ssh/sshd_config.bak.20260326 /etc/ssh/sshd_config
sudo systemctl restart sshd
예방적 자동 복구 스크립트
포트 변경 전에 5분 뒤 자동으로 22 포트로 복구하는 at 잡을 예약해 두는 방법:
# 5분 후 자동 복구 예약 (새 포트 테스트 시간 확보)
echo "cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config && systemctl restart sshd" | sudo at now + 5 minutes
# 새 포트 변경 적용
sudo sed -i 's/^Port 22$/Port 2222/' /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart sshd
# 새 포트로 접속 테스트 (다른 터미널에서)
ssh -p 2222 alice@server
# 테스트 성공 시: at 잡 취소
sudo atq # 잡 번호 확인
sudo atrm <번호> # 자동 복구 잡 취소
# 테스트 실패 시: 5분 기다리면 22 포트로 자동 복구됨
상황: 패스프레이즈 설정한 키로 접속할 때마다 비밀번호를 묻거나, 키가 여러 개일 때 Too many authentication failures로 접속이 끊깁니다.
원인: ssh-agent에 키가 등록되지 않았거나, IdentitiesOnly yes 없이 여러 키를 순차 시도하다 MaxAuthTries 초과합니다.
해결
증상 1: 매번 패스프레이즈 입력 요구
ssh dev
Enter passphrase for key '/home/alice/.ssh/id_ed25519':
해결: ssh-agent에 키 등록
# ssh-agent 실행 (이미 실행 중이면 생략)
eval "$(ssh-agent -s)"
Agent pid 18523
# 키 등록 (패스프레이즈 한 번만 입력)
ssh-add ~/.ssh/id_ed25519
Enter passphrase for /home/alice/.ssh/id_ed25519:
Identity added: /home/alice/.ssh/id_ed25519 (work@mycompany.com)
# 등록된 키 목록 확인
ssh-add -l
256 SHA256:abc123XYZqwerty789mn work@mycompany.com (ED25519)
# 이후 패스프레이즈 없이 접속 가능
ssh dev
~/.bashrc 또는 ~/.zshrc에 자동화
# ~/.bashrc 하단에 추가
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)" > /dev/null 2>&1
ssh-add ~/.ssh/id_ed25519 2>/dev/null
fi
증상 2: Too many authentication failures
여러 키 파일이 있을 때 SSH가 모든 키를 순차적으로 시도하다가 MaxAuthTries를 초과합니다.
# 해결: IdentitiesOnly yes로 지정한 키만 사용
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 alice@server
# 또는 ~/.ssh/config에 영구 설정
Host dev
HostName 192.168.10.10
User alice
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes # ← 지정한 키만 시도
증상 3: WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING A MITM ATTACK!
서버를 재설치했거나 IP가 재사용된 경우 발생합니다. MITM 공격이 아님을 확인한 후:
# known_hosts에서 해당 항목 제거
ssh-keygen -R 192.168.10.10
ssh-keygen -R "192.168.10.10:2222" # 비표준 포트
# 재접속하면 새 호스트 키를 신뢰할지 묻는 프롬프트 표시
ssh dev
The authenticity of host '192.168.10.10 (192.168.10.10)' can't be established.
ED25519 key fingerprint is SHA256:newFingerprint123...
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
실무 시나리오: 프로덕션 인프라에서는 내부 서버들이 직접 인터넷에 노출되지 않고, **Bastion Host(점프 서버)**를 통해서만 접근 가능하도록 구성합니다. Bastion Host는 VPC/내부망의 관문 역할을 하며, 모든 SSH 접근이 이 서버를 경유합니다.
일반적인 아키텍처
Public Subnet에 Bastion Host만 공개 IP로 노출하고, 내부 서버들은 Private Subnet에 두어 Bastion을 통해서만 접근하도록 구성합니다.
인터넷
│
▼
┌─────────────────────────────────────┐
│ Public Subnet │
│ ┌───────────────┐ │
│ │ Bastion Host │ 공개 IP 보유 │
│ │ (jump-server) │ SSH 2222 오픈 │
│ └───────┬───────┘ │
└──────────│──────────────────────────┘
│ 내부 네트워크
┌──────────│──────────────────────────┐
│ Private Subnet │
│ ┌───────▼──────┐ ┌───────────────┐ │
│ │ web-01 │ │ db-01 │ │
│ │ 10.0.1.10 │ │ 10.0.2.20 │ │
│ └──────────────┘ └───────────────┘ │
│ ┌───────────────┐ │
│ │ app-01 │ │
│ │ 10.0.1.20 │ │
│ └───────────────┘ │
└─────────────────────────────────────┘
ProxyJump 설정 (OpenSSH 7.3+)
ProxyJump를 ~/.ssh/config에 설정하면 내부 서버들을 마치 직접 접속하는 것처럼 ssh web-01 한 줄로 Bastion을 통해 연결할 수 있습니다.
# ~/.ssh/config
# Bastion Host
Host bastion
HostName bastion.mycompany.com
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 30
ServerAliveCountMax 3
# 내부 웹 서버 (Bastion 경유)
Host web-01
HostName 10.0.1.10
User ubuntu
Port 22
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
# 내부 앱 서버
Host app-01
HostName 10.0.1.20
User ubuntu
Port 22
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
# DB 서버 (보안상 별도 키 사용)
Host db-01
HostName 10.0.2.20
User dbadmin
Port 22
IdentityFile ~/.ssh/id_db_prod
ProxyJump bastion
# 다단계 점프 (Bastion → 중간 서버 → 최종 목적지)
Host deep-internal
HostName 172.16.0.50
User admin
ProxyJump bastion,app-01
사용 방법
설정 후에는 Bastion 경유가 투명하게 처리됩니다. ssh, scp, rsync 모두 동일한 별칭으로 씁니다.
# 한 번에 내부 서버로 접속 (Bastion 자동 경유)
ssh web-01
# 접속 과정 (투명하게 처리됨)
# 1. bastion.mycompany.com:2222 접속
# 2. bastion에서 10.0.1.10:22 연결 생성
# 3. 단일 SSH 세션처럼 사용
# scp도 동일하게 동작
scp ./deploy.tar.gz web-01:/var/www/releases/
# rsync도 동일
rsync -avz ./dist/ web-01:/var/www/html/
ProxyJump 없이 명령줄로 직접 지정 (임시 사용)
~/.ssh/config에 등록하지 않고 일회성으로 Bastion을 경유할 때는 -J 옵션을 씁니다.
# -J 옵션으로 점프 서버 지정
ssh -J alice@bastion.mycompany.com:2222 ubuntu@10.0.1.10
# 다단계
ssh -J alice@bastion:2222,ubuntu@10.0.1.10 dbadmin@172.16.0.50
ForwardAgent vs ProxyJump 비교
두 방식 모두 Bastion을 경유하지만 개인키 노출 위험이 다릅니다.
| 방식 | 개인키 전송 | 보안성 | 권장 |
|---|---|---|---|
ForwardAgent yes | Agent forwarding으로 간접 전달 | Bastion 관리자가 악용 가능 | 신뢰된 환경만 |
ProxyJump | 개인키가 Bastion에 전혀 저장 안 됨 | 높음 | 권장 |
ProxyJump는 Bastion이 단순히 TCP 연결을 중계하는 역할만 하므로, Bastion에 개인키가 필요 없고 Bastion 서버가 침해당해도 내부 서버 키가 노출되지 않습니다.
Bastion Host 자체 보안 강화
Bastion은 모든 SSH 트래픽의 관문이므로 일반 서버보다 더 엄격하게 설정합니다.
# Bastion의 sshd_config 권장 설정
AllowUsers alice bob ops-team
PasswordAuthentication no
PermitRootLogin no
MaxAuthTries 3
Port 2222
# AllowTcpForwarding: 내부 서버로의 포워딩은 허용, 그 외 제한
AllowTcpForwarding yes
X11Forwarding no
AllowAgentForwarding no # ForwardAgent 대신 ProxyJump 사용하므로 불필요
# 접속 기록 강화 (auditd 또는 rsyslog)
# 모든 SSH 세션을 중앙 로그 서버로 전송
접속 감사(Audit) 설정
LogLevel VERBOSE를 설정하면 어떤 키로 누가 접속했는지 핑거프린트를 로그에 남겨 사후 감사가 가능합니다.
# /etc/ssh/sshd_config에 추가
LogLevel VERBOSE # 키 핑거프린트를 로그에 기록
# 접속 로그 확인
sudo journalctl -u sshd | grep "Accepted publickey"
Mar 26 09:45:12 bastion sshd[2341]: Accepted publickey for alice from 203.0.113.5 port 52341 ssh2: ED25519 SHA256:abc123XYZ
Mar 26 09:45:12 bastion sshd[2341]: pam_unix(sshd:session): session opened for user alice
팀 규모가 커질수록 필요한 SSH 키 관리 전략
1. 환경별 키 분리
~/.ssh/
├── id_ed25519 # 개인 기본키
├── id_ed25519.pub
├── id_prod # 프로덕션 전용 키 (더 엄격하게 관리)
├── id_prod.pub
├── id_staging # 스테이징 전용 키
├── id_staging.pub
├── id_github # GitHub/GitLab 전용
├── id_github.pub
└── config
2. CI/CD 자동화용 Deploy Key 패턴
CI/CD 파이프라인에서 서버에 배포할 때는 패스프레이즈 없는 전용 키를 사용하고, authorized_keys에 실행 가능한 명령을 제한하여 보안을 유지합니다.
# 배포 전용 키 생성 (패스프레이즈 없음, 용도 명시)
ssh-keygen -t ed25519 -f ~/.ssh/id_deploy_prod -N "" -C "github-actions-deploy-prod-$(date +%Y%m%d)"
# authorized_keys에 명령어 제한으로 등록
# 배포 스크립트만 실행 가능, 대화형 쉘 불가
cat ~/.ssh/id_deploy_prod.pub
# 서버의 authorized_keys에 아래 형식으로 등록:
command="/usr/local/bin/deploy.sh",no-pty,no-agent-forwarding,no-x11-forwarding,no-port-forwarding ssh-ed25519 AAAA... github-actions-deploy-prod-20260326
3. ServerAliveInterval로 세션 유지
장시간 작업 중 비활성으로 세션이 끊기는 것을 방지합니다.
# ~/.ssh/config 전역 설정
Host *
ServerAliveInterval 60 # 60초마다 keepalive 패킷 전송
ServerAliveCountMax 3 # 3번 응답 없으면 연결 끊음 (총 3분)
TCPKeepAlive yes # TCP 레벨 keepalive도 활성화
4. SSH 키 만료 및 교체 정책
키 코멘트에 날짜와 용도를 명시해두면 오래된 키를 찾아 제거하기 쉬워집니다. 정기적으로 퇴사자 키와 유효기간 지난 키를 정리하세요.
# 현재 authorized_keys에 등록된 키 목록 확인 (코멘트로 날짜/용도 파악)
cat ~/.ssh/authorized_keys | awk '{print $3}'
# 오래된 키 제거 후 저장
vi ~/.ssh/authorized_keys
# 또는
grep -v "old-key-2024" ~/.ssh/authorized_keys > /tmp/ak_tmp && mv /tmp/ak_tmp ~/.ssh/authorized_keys
5. 다수의 서버에 키 일괄 배포 (Ansible 방식)
서버가 수십 대 이상이면 ssh-copy-id를 수동으로 실행하는 것은 현실적이지 않습니다. Ansible의 authorized_key 모듈로 일괄 배포하거나 제거할 수 있습니다.
# ansible의 authorized_key 모듈 활용
# inventory.yml에 서버 목록 정의 후
ansible all -m authorized_key -a "user=alice key='$(cat ~/.ssh/id_ed25519.pub)' state=present"
# 퇴사자 키 일괄 제거
ansible all -m authorized_key -a "user=alice key='$(cat old_key.pub)' state=absent"
6. SSH 접속 감사 로그 중앙 수집
서버별 로그를 각 서버에서 개별 확인하면 보안 사고 후 추적이 어렵습니다. rsyslog로 SSH 로그를 중앙 서버로 모아두면 "누가 언제 어떤 서버에 접속했는지"를 한 곳에서 조회할 수 있습니다.
# rsyslog로 SSH 로그를 중앙 서버로 전송
# /etc/rsyslog.d/ssh-audit.conf
echo 'if $programname == "sshd" then @logserver.internal:514' | sudo tee /etc/rsyslog.d/ssh-audit.conf
sudo systemctl restart rsyslog
# 중앙 서버에서 누가 언제 어디서 접속했는지 확인
grep "Accepted publickey" /var/log/remote/*/secure | sort -k1,2
7. 빠른 SSH 문제 진단 체크리스트
SSH 접속이 안 될 때 네트워크부터 방화벽까지 단계별로 좁혀가면 빠르게 원인을 찾을 수 있습니다.
접속 안 될 때 확인 순서:
1. 네트워크 연결
ping <서버IP>
telnet <서버IP> <포트>
2. 서버 SSH 포트 오픈 여부
nc -zv <서버IP> 2222
nmap -p 2222 <서버IP>
3. 클라이언트 키/설정
ssh -vvv alice@server 2>&1 | head -50
4. 서버 sshd 상태
sudo systemctl status sshd
sudo journalctl -u sshd -n 20
5. 권한 문제
ls -la ~/.ssh/
stat ~/.ssh/authorized_keys
6. fail2ban 차단 여부
sudo fail2ban-client status sshd
7. 방화벽 규칙
sudo firewall-cmd --list-all
sudo iptables -L -n | grep 2222
SSH CA 인증서 기반 운영 — 대규모 키 관리 단순화

서버가 수십 대 이상으로 늘어나면 authorized_keys 파일 관리가 병목이 됩니다. 각 서버마다 공개키를 추가/제거해야 하므로 퇴사자 처리나 키 로테이션이 누락되기 쉽습니다. SSH CA(인증서 기반 인증) 는 이 문제를 근본적으로 해결합니다.
기본 원리:
[관리자 CA] → 사용자 공개키에 서명 → SSH 인증서 발급
[서버] → CA 공개키만 신뢰하도록 설정
[사용자] → 인증서로 모든 서버에 접속 (서버별 authorized_keys 불필요)
CA 키 생성 및 서버 설정:
# 1. CA 키 쌍 생성 (별도 보안 서버에서)
ssh-keygen -t ed25519 -f /etc/ssh/ssh_ca -C "infra-ca-2025"
# /etc/ssh/ssh_ca ← CA 개인키 (매우 안전하게 보관!)
# /etc/ssh/ssh_ca.pub ← CA 공개키 (모든 서버에 배포)
# 2. 각 서버 sshd_config에 CA 공개키 신뢰 설정
echo "TrustedUserCAKeys /etc/ssh/trusted_ca.pub" | sudo tee -a /etc/ssh/sshd_config
sudo cp ssh_ca.pub /etc/ssh/trusted_ca.pub
sudo systemctl reload sshd
사용자 인증서 발급:
# 사용자 alice의 공개키에 CA로 서명 (유효기간 30일)
ssh-keygen -s /etc/ssh/ssh_ca \
-I "alice@infra-2025-04-05" \ # 인증서 식별자 (감사 로그에 표시)
-n alice,deploy \ # 허용 사용자명 목록
-V +30d \ # 유효기간 30일
alice_id_ed25519.pub
# 결과: alice_id_ed25519-cert.pub 생성
# 인증서 내용 확인
ssh-keygen -L -f alice_id_ed25519-cert.pub
접속 테스트:
# alice가 인증서로 서버에 접속
ssh -i ~/.ssh/alice_id_ed25519 -i ~/.ssh/alice_id_ed25519-cert.pub server
# 서버에서 인증서 접속 로그 확인
grep "certificate" /var/log/auth.log
# sshd: Accepted publickey for alice ... ID alice@infra-2025-04-05 CA ...
운영 장점 요약:
| 방식 | 키 로테이션 시 | 퇴사자 처리 |
|---|---|---|
| 전통적 authorized_keys | 서버마다 파일 수정 필요 | 서버마다 키 제거 필요 |
| SSH CA 인증서 | CA 재서명 (중앙 1곳) | 인증서 만료 또는 revocation list |
주의: CA 개인키 유출은 모든 서버 접근을 위협합니다. CA 키는 HSM(Hardware Security Module)이나 Vault 같은 시크릿 관리 시스템에 보관하고, 서명 작업을 자동화 파이프라인으로 격리하세요.
다음 모듈에서는 서버 장애 초동 대응 — 장애 발생 후 5분 안에 원인을 좁히는 트리아지 체크리스트와 top, free, iostat, dmesg 조합으로 진단하는 방법을 다룹니다.