13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 서버 보안 강화

  • [Linux] firewalld 보안 체크리스트: Linux 서버 방화벽 설정 모범 사례

    [Linux] firewalld 보안 체크리스트: Linux 서버 방화벽 설정 모범 사례

    firewalld 보안 체크리스트: Linux 서버 방화벽 설정 모범 사례

    리눅스 서버를 오래 운영하다 보면, 서비스는 멀쩡한데 방화벽이 애매하게 열려 있어서 뒤늦게 식은땀이 나는 순간이 꼭 옵니다. 저도 홈랩이랑 운영 서버를 같이 보면서 몇 번이나 겪었거든요. 특히 배포 직후에는 패키지 설치, TLS, 애플리케이션 기동에 신경이 쏠리다 보니 firewalld 보안 체크리스트가 뒤로 밀리기 쉽습니다. 그런데 실제 사고는 대개 여기서 납니다. CVE가 바로 터지지 않더라도, 불필요하게 열린 관리 포트 하나가 공격 표면을 넓히고, 나중에는 “왜 이 포트가 열려 있지?”를 추적하는 운영 비용으로 돌아오더라고요.

    이 글은 명령어만 늘어놓는 글이 아닙니다. 저는 firewalld를 볼 때 “무엇을 열까”보다 “왜 이 경계가 필요한가, 어디서 망가지기 쉬운가, 변경 후 어떻게 검증할까”를 더 중요하게 봅니다. 그래서 이번 글은 Linux 방화벽 설정을 점검하는 순서, 자주 꼬이는 실패 모드의 원인, 단일 NIC 서버와 듀얼 NIC 서버에서 판단이 갈리는 지점까지 실무 기준으로 묶었습니다. 체크박스만 채우는 요약이 아니라, 실제로 복붙해서 점검하고 운영 습관까지 바꿀 수 있는 내용으로 정리해보겠습니다.

    firewalld 보안 체크리스트를 설명하는 Linux 서버 방화벽 아키텍처 이미지

    firewalld의 Zone(존), Service(서비스), Port(포트)가 어떻게 맞물려 동작하는지 한눈에 보여주는 개요 이미지입니다.

    1. 왜 firewalld 보안 체크리스트가 먼저냐

    제가 운영 초기에 가장 많이 본 실수는 “애플리케이션이 정상 응답하니 네트워크도 정리된 줄 아는 상태”였습니다. 실제로는 반대인 경우가 많습니다. 앱은 443만 써야 하는데, 테스트 때 열어둔 8080, 관리용 9090, 배포판 기본 서비스까지 같이 살아 있는 식이죠. firewalld는 이런 상태를 정리하기에 꽤 좋은 도구입니다. 다만 편한 도구라고 해서 자동으로 안전해지지는 않습니다. 규칙을 추가하기 쉬운 도구일수록, 나중에 덜어내는 기준이 더 중요합니다.

    제가 실무에서 기준으로 삼는 건 네 가지입니다. 첫째, 인터넷에 공개할 이유가 없는 포트는 기본적으로 닫는다. 둘째, 관리 트래픽은 서비스 트래픽과 다른 경계로 본다. 셋째, 런타임 실험과 영구 반영을 섞지 않는다. 넷째, 명령 성공이 아니라 실제 패킷 경로 기준으로 검증한다. 방화벽은 설정 자체보다 운영 중 일관성이 더 중요합니다.

    • 최소 허용: 서비스가 실제로 수신해야 하는 프로토콜과 포트만 엽니다.
    • 경계 분리: 공개망, 관리망, 백업망을 같은 존 정책으로 처리하지 않습니다.
    • 상태 분리: 런타임 테스트와 --permanent 반영을 구분합니다.
    • 검증 우선: firewall-cmd 출력만 보지 말고 리슨 상태와 외부 접속 결과까지 확인합니다.

    2. firewalld 보안 체크리스트의 핵심 개념

    처음에 많이 헷갈리는 부분이 Zone, Service, Port, Rich Rule이 각각 뭘 책임지는지입니다. 저는 이걸 “정책의 단위가 다르다”로 이해하는 편이 가장 덜 꼬였습니다. Zone은 경계, Service는 의미 있는 허용 단위, Port는 예외 처리, Rich Rule은 조건부 허용입니다. 이걸 섞어 쓰더라도 각자 맡는 역할이 분명해야 나중에 규칙 해석이 쉬워집니다.

    2-1. Zone(존, 신뢰 수준별 정책 그룹)

    Zone은 인터페이스 또는 소스 대역에 붙는 정책 경계입니다. 중요한 건 “서버 한 대 = 정책 한 개”가 아니라는 점입니다. NIC가 하나여도 출발지 대역이 다르면 다른 경계로 볼 수 있고, NIC가 둘 이상이면 더더욱 분리하는 게 맞습니다. 운영하면서 제일 위험한 패턴은 외부 공개 NIC와 관리자 전용 네트워크를 둘 다 public에 묶어두는 경우였습니다. 이렇게 해두면 나중에 SSH, 백업, 모니터링 예외가 전부 public에 쌓이면서 정책이 지저분해집니다.

    2-2. Service(서비스, 미리 정의된 포트 집합)

    ssh, http, https 같은 표준 서비스는 Service로 여는 편이 낫습니다. 숫자 포트보다 의도가 남기 때문입니다. 나중에 누가 봐도 “이 서버가 왜 이 포트를 열었는지”를 해석하기 쉽습니다. 다만 모든 걸 Service로 해결하려고 하면 오히려 애매해질 수 있습니다. 예를 들어 커스텀 앱 8443/tcp를 장기 운영하면서 이름 없는 포트로만 남겨두면 의미를 잃고, 반대로 잠깐 쓸 포트까지 서비스 XML로 만드는 건 과합니다.

    2-3. Runtime과 Permanent

    여기가 제일 많이 사고 나는 지점입니다. 런타임은 현재 커널에 적용된 메모리 상태이고, 영구 설정은 디스크에 저장된 상태입니다. 즉, 테스트할 때는 런타임이 빠르지만 운영 기준은 결국 영구 설정입니다. 문제는 두 상태가 잠시 어긋나도 눈으로는 잘 안 보인다는 점이죠. 그래서 저는 “테스트는 런타임, 반영은 영구, 마감은 reload 후 재검증” 순서를 고정해둡니다. 이 흐름을 지키면 재부팅 뒤 접속 불가 같은 사고가 확 줄어듭니다.

    선택지 언제 쓰면 좋은가 장점 언제 피해야 하는가 운영상 주의점
    Service 추가 SSH, HTTP, HTTPS 같은 표준 포트 의도가 명확하고 읽기 쉽습니다 실제 포트 구성이 표준과 다를 때 --info-service로 정의 내용을 확인해두면 좋습니다
    Port 직접 허용 커스텀 앱, 단기 테스트, 비표준 포트 즉시 반영이 쉽습니다 장기 운영 포트를 설명 없이 누적할 때 운영 문서에 포트 용도를 같이 남겨야 합니다
    Rich Rule 출발지 IP 제한, 로그, 세밀한 예외 처리 서비스 공개 범위를 정확히 좁힐 수 있습니다 단순한 허용 규칙까지 전부 Rich Rule로 만들 때 규칙이 많아지면 읽기 어려워지므로 목적별로 묶어야 합니다
    Zone 분리 NIC가 둘 이상이거나 관리망/서비스망이 분리된 환경 경계가 명확해지고 예외가 줄어듭니다 작은 단일 NIC 서버에서 과도하게 복잡도를 키울 때 인터페이스 매핑과 소스 대역 매핑을 혼동하지 않아야 합니다
    커스텀 Service XML 장기 운영하는 사내 표준 앱 포트 포트 의미를 이름으로 보존할 수 있습니다 일회성 포트 하나만 잠깐 열 때 /etc/firewalld/services/에 정의를 남기고 변경 이력을 관리하면 편합니다

    3. 실전 체크리스트 1단계: 현재 상태부터 정확히 파악하기

    방화벽 작업에서 제일 위험한 건 규칙 추가 자체가 아니라, 현재 상태를 오해한 채 손대는 겁니다. 원격 SSH 세션 하나로 작업 중인데 현재 세션이 어느 존 정책에 의해 살아 있는지, 같은 서버에 다른 관리 포트가 떠 있는지, 런타임과 영구 설정이 같은지 모르고 건드리면 끊어먹기 쉽습니다. 그래서 저는 항상 “서비스 상태 → 활성 존 → 인터페이스 매핑 → 허용 항목 → 리슨 상태” 순서로 봅니다. 이 순서가 의외로 사고를 많이 줄여줍니다.

    1. firewalld 서비스가 실제로 올라와 있는지 확인합니다.
    2. 기본 Zone과 활성 Zone이 같은지 봅니다.
    3. 어떤 인터페이스가 어느 Zone에 붙었는지 확인합니다.
    4. Zone별 허용 서비스, 포트, Rich Rule을 나눠 봅니다.
    5. 실제 프로세스가 어떤 주소와 포트에 바인딩되어 있는지 비교합니다.
    sudo systemctl status firewalld
    sudo firewall-cmd --state
    sudo firewall-cmd --get-default-zone
    sudo firewall-cmd --get-active-zones
    sudo firewall-cmd --zone=public --list-all
    sudo firewall-cmd --list-all-zones
    sudo ss -tulpn

    여기서 읽는 기준이 중요합니다. --get-active-zones는 “정책 이름”보다 “어느 인터페이스나 소스가 어디에 매핑됐는지”를 보는 명령이라고 생각하시면 됩니다. --list-all에서는 services, ports, rich rules를 따로 봐야 합니다. 운영 현장에서 진짜 자주 보는 실수가 서비스만 보고 안심하는 겁니다. 예전에 열어둔 포트가 ports:에 남아 있는데 services:만 보고 지나가면 정책 오판이 생깁니다.

    한 가지 더 보셔야 할 게 있습니다. ss -tulpn 결과와 firewalld 허용 목록이 맞는지 비교해야 합니다. 앱이 0.0.0.0:9090에 떠 있는데 방화벽에서 9090이 닫혀 있으면 외부 노출은 막히겠지만, 내부 망이나 로컬 프록시 경로에서는 다른 문제가 생길 수 있습니다. 반대로 앱이 127.0.0.1에만 바인딩되어 있으면 포트를 아무리 열어도 외부 접속은 안 됩니다. 보안 점검과 장애 점검이 이 지점에서 만납니다.

    제가 실무에서 자주 쓰는 확인 패턴은 아래처럼 “방화벽 상태와 실제 리슨 상태를 한 번에 비교 가능한 형태”로 보는 방식입니다.

    sudo firewall-cmd --get-active-zones
    sudo firewall-cmd --zone=public --list-services
    sudo firewall-cmd --zone=public --list-ports
    sudo firewall-cmd --zone=public --list-rich-rules
    sudo ss -lntp
    sudo ss -lnup
    firewalld 보안 체크리스트 점검을 위한 Linux 방화벽 설정 터미널 이미지

    활성 Zone, 인터페이스 매핑, 허용 서비스와 포트를 점검하는 실제 운영자 시점의 터미널 화면을 표현한 이미지입니다.

    4. 실전 체크리스트 2단계: 필요한 것만 열고 나머지는 닫기

    이 단계부터는 정책을 정리합니다. 기준은 단순합니다. “지금 열려 있는 것”이 아니라 “이 서버 역할에 꼭 필요한 것”만 남기는 겁니다. 웹 서버라면 대개 공개 포트는 80/443 중 필요한 것만 남고, SSH는 관리 경로로만 제한합니다. 이때 중요한 건 보안을 이유로 운영성을 망치지 않는 겁니다. 예를 들어 SSH 전체 차단은 멋있어 보일 수 있지만, 콘솔 대체 수단이 없으면 사고 복구 시간을 키웁니다. 그래서 저는 외부 공개 서버에서 가장 현실적인 기본형을 HTTPS 공개 + SSH 출발지 제한 + 불필요한 기본 서비스 제거로 잡습니다.

    4-1. 기본 서비스만 먼저 허용

    sudo firewall-cmd --permanent --zone=public --add-service=https
    sudo firewall-cmd --permanent --zone=public --add-service=http
    sudo firewall-cmd --permanent --zone=public --remove-service=cockpit
    sudo firewall-cmd --permanent --zone=public --remove-service=dhcpv6-client
    sudo firewall-cmd --reload
    sudo firewall-cmd --zone=public --list-all

    여기서 판단 기준을 분명히 하셔야 합니다. http는 80에서 443으로 리다이렉트하는 프런트가 있거나 ACME HTTP-01 검증이 필요하면 열고, 그렇지 않으면 빼도 됩니다. cockpit은 명시적으로 운영할 때만 여는 편이 안전합니다. 다만 cockpit이나 dhcpv6-client가 실제로 기본 등록되어 있는지는 배포판과 초기 설정에 따라 다를 수 있으니, 제거 전에 --list-all로 현재 상태를 먼저 보세요. 운영 보안은 결국 기본값을 그대로 믿지 않는 데서 시작합니다.

    4-2. SSH는 전체 공개보다 출발지 IP 제한

    SSH는 “열까 말까”보다 “누구에게 열까”가 더 중요합니다. 운영 서버에서 22/tcp 전체 공개는 공격 표면을 넓히는 데 비해 얻는 편의가 적습니다. 고정 관리 IP나 VPN 대역이 있다면 Rich Rule로 제한하는 쪽이 좋습니다. 특히 fail2ban 같은 보조 수단을 쓰더라도, 방화벽에서 먼저 줄이는 게 낫습니다. 로그인 실패를 막는 것보다 애초에 소켓 도달 범위를 좁히는 편이 더 근본적이거든요.

    sudo firewall-cmd --permanent --zone=public \
      --add-rich-rule='rule family="ipv4" source address="203.0.113.10/32" service name="ssh" accept'
    
    sudo firewall-cmd --permanent --zone=public --remove-service=ssh
    sudo firewall-cmd --reload
    sudo firewall-cmd --zone=public --list-rich-rules

    이 규칙의 핵심은 단순합니다. ssh 서비스를 전체 공개하지 않고, 특정 출발지에서만 받아줍니다. 관리 대역이 VPN 서브넷이라면 203.0.113.10/32 대신 실제 CIDR을 넣으시면 됩니다. 다만 여기서 흔한 실패는 “회사 공인 IP라고 생각했던 주소가 실제로는 NAT 뒤에서 바뀌는 주소”인 경우입니다. 그러면 방화벽 문제처럼 보이는데 사실은 출발지 식별 가정이 틀린 겁니다. 그래서 이 단계는 현재 세션을 유지한 채 새 터미널에서 재접속 테스트까지 같이 하시는 게 안전합니다.

    4-3. 커스텀 포트는 무조건 열지 말고, 수명에 따라 처리 방식 나누기

    8443, 9090, 3000 같은 커스텀 포트는 운영에서 자주 나옵니다. 저는 이걸 수명 기준으로 나눕니다. 일시적인 디버그 포트면 짧게 열고 바로 닫습니다. 장기 운영 포트면 처음부터 이름 있는 서비스로 승격할지, 포트로 두되 문서화할지 정합니다. 이걸 안 정하면 몇 달 뒤 포트 목록이 기술 부채가 됩니다.

    sudo firewall-cmd --permanent --zone=public --add-port=8443/tcp
    sudo firewall-cmd --permanent --zone=public --remove-port=9090/tcp
    sudo firewall-cmd --reload
    sudo firewall-cmd --zone=public --list-ports

    장기 운영 포트가 있고 팀원이 여러 명이라면 커스텀 서비스 정의를 고려할 만합니다. 포트를 숫자로만 남겨두면 “8443이 무슨 용도였지?”가 반복됩니다. 반대로 운영 수명이 짧은 포트는 XML 정의까지 만들 필요는 없습니다. 이런 선택 기준이 실무에서 시간을 아껴줍니다.

    <?xml version="1.0" encoding="utf-8"?>
    <service>
      <short>internal-api</short>
      <description>Internal API exposed via TCP 8443</description>
      <port protocol="tcp" port="8443"/>
    </service>

    위 같은 파일을 /etc/firewalld/services/internal-api.xml에 두고 reload한 뒤 --add-service=internal-api로 관리하면, 숫자 포트보다 정책 의미가 오래 남습니다. 저는 포트가 배포 문서, 모니터링, 보안 정책에 반복 등장하기 시작하면 이 단계로 올립니다.

    5. 실전 체크리스트 3단계: Zone 분리와 인터페이스 매핑

    NIC가 둘 이상인 서버에서는 여기서 보안 수준이 꽤 갈립니다. 공개 트래픽과 관리 트래픽을 같은 존에 넣어두면, 나중에 허용 규칙이 죄다 public으로 몰립니다. 그 결과 “관리 편의를 위해 public 예외를 하나 더 추가”하는 패턴이 반복되고, 결국 원래 분리했어야 할 경계가 사라집니다. 제가 듀얼 NIC 서버에서 가장 먼저 정리하는 게 이 부분입니다.

    sudo firewall-cmd --permanent --new-zone=management
    sudo firewall-cmd --permanent --zone=management --add-service=ssh
    sudo firewall-cmd --permanent --zone=management --add-source=192.168.10.0/24
    sudo firewall-cmd --permanent --zone=public --change-interface=eth0
    sudo firewall-cmd --permanent --zone=management --change-interface=eth1
    sudo firewall-cmd --reload
    sudo firewall-cmd --get-active-zones

    이 구성이 좋은 이유는 규칙을 줄여주기 때문입니다. 외부 서비스망 eth0는 공개 포트만 남기고, 내부 관리망 eth1은 SSH, 백업, 모니터링처럼 성격이 다른 트래픽을 따로 받습니다. 운영하면서 예외가 생기더라도 public을 더럽히지 않고 management 쪽에서 해결할 수 있습니다.

    다만 언제나 Zone 분리가 정답은 아닙니다. NIC가 하나뿐이고 관리도 VPN 하나로만 들어온다면, 억지로 존을 많이 만드는 것보다 public 하나에 출발지 제한 Rich Rule을 두는 편이 단순하고 덜 위험합니다. 정책은 강한 것보다 읽기 쉬운 것이 오래 갑니다. 제가 권하는 기준은 이렇습니다. 인터페이스가 다르면 Zone 분리, 인터페이스는 같고 출발지만 다르면 Rich Rule 우선. 이게 대부분의 중소형 서버에서 가장 덜 꼬입니다.

    한 가지 더 실무 팁을 드리면, NetworkManager가 인터페이스-존 매핑을 함께 관리하는 환경에서는 firewalld 쪽 변경과 네트워크 연결 프로필 설정이 엇갈리지 않게 맞춰야 합니다. 그래서 저는 관리망이 명확한 환경에서는 “관리 NIC는 management로 고정, VPN이나 사내 대역은 source로 보강” 정도까지만 씁니다. 규칙 해석이 복잡해지는 순간, 보안 자체보다 운영 오류가 더 큰 리스크가 됩니다.

    Linux 방화벽 설정에서 firewalld 존 분리를 보여주는 서버 보안 강화 이미지

    외부 서비스망과 내부 관리망을 분리한 듀얼 NIC 환경의 Zone 설계를 이해하기 위한 다이어그램입니다.

    6. 실제로 많이 꼬이는 포인트와 트러블슈팅

    이 섹션은 공식 문서보다 운영에서 더 많이 필요합니다. 명령 자체는 맞는데 결과가 기대와 다를 때, 대개 원인은 문법보다 가정이 틀린 데 있습니다. 제가 실제로 자주 본 실패 모드만 추렸습니다. 이 부분을 미리 알고 들어가면 대응 속도가 확실히 빨라집니다.

    6-1. 재부팅 후 규칙이 사라진다

    대부분 원인은 단순합니다. 런타임에만 넣고 영구 반영을 안 했거나, 반대로 런타임과 영구 상태가 이미 달라져 있었는데 작업자가 둘을 같은 것으로 착각한 경우입니다. 근본 원인은 “테스트 상태와 배포 상태를 구분하지 않는 습관”입니다.

    sudo firewall-cmd --list-all
    sudo firewall-cmd --permanent --zone=public --list-all
    sudo firewall-cmd --runtime-to-permanent
    sudo firewall-cmd --reload

    --runtime-to-permanent는 편하지만 무심코 쓰면 임시로 열어둔 포트까지 저장해버릴 수 있습니다. 그래서 저는 이 명령을 “좋은 상태가 이미 런타임에 올라와 있다는 걸 확인한 뒤”에만 씁니다. 확신이 없으면 아예 영구 설정에 다시 명시하는 편이 낫습니다.

    6-2. SSH 접속이 갑자기 끊긴다

    이건 규칙 문법 문제보다 작업 순서 문제입니다. 보통은 --remove-service=ssh를 너무 일찍 적용했거나, 허용해야 할 출발지 IP를 잘못 넣었거나, 현재 접속 세션이 예상과 다른 경로를 타고 있었던 경우입니다. 예를 들어 평소엔 VPN으로 들어오지만 오늘은 외부 회선에서 접속한 상태라면, 머릿속 관리 IP와 실제 출발지 IP가 다를 수 있습니다.

    • 현재 SSH 세션을 끊지 말고, 새 터미널에서 재접속 테스트를 먼저 합니다.
    • 허용 규칙 추가 후 --remove-service=ssh를 마지막에 실행합니다.
    • 클라우드 서버면 웹 콘솔이나 시리얼 콘솔 경로를 먼저 확보합니다.
    • who, ss -tnp | grep :22 같은 보조 확인으로 현재 세션 상태를 같이 봅니다.

    6-3. 애플리케이션은 떠 있는데 외부 접속이 안 된다

    이럴 때 방화벽만 붙잡고 있으면 오래 갑니다. 실제 원인은 앱이 127.0.0.1에만 바인딩되어 있거나, 컨테이너 프록시와 호스트 포트가 다르거나, 다른 존에 인터페이스가 붙어 있어서 예상한 정책을 안 타는 경우가 많습니다. 즉, 근본 원인은 “포트 개방”과 “서비스 바인딩”을 같은 것으로 보는 데 있습니다.

    sudo ss -tulpn
    sudo firewall-cmd --get-active-zones
    sudo firewall-cmd --zone=public --list-ports
    sudo firewall-cmd --zone=public --list-services

    읽는 기준은 이렇습니다. ss에서 앱이 실제 외부 IP 또는 0.0.0.0에 바인딩되어 있어야 하고, 해당 포트가 맞는 존에서 허용되어 있어야 합니다. 둘 중 하나라도 빠지면 접속이 안 됩니다. 간단해 보이는데, 장애 대응에서는 이 두 층을 분리해서 보지 않아서 시간이 오래 가더라고요.

    6-4. 규칙은 맞는 것 같은데 이상하게 다른 트래픽도 통과한다

    이건 종종 방화벽이 아니라 경로 문제입니다. 예를 들어 애플리케이션이 같은 호스트의 로컬 프록시를 통해 우회하고 있거나, 컨테이너 네트워크 규칙이 별도로 적용되거나, 클라우드 보안 그룹이 firewalld보다 앞단에서 이미 허용하거나 차단하고 있을 수 있습니다. firewalld는 유일한 경계가 아닙니다. 제가 운영 점검할 때 항상 같이 묻는 질문이 “이 트래픽이 진짜 호스트 ingress 경로를 타는가?”입니다.

    7. firewalld 보안 체크리스트의 검증 방법

    보안 설정은 명령 성공으로 끝나면 안 됩니다. 핵심은 “허용된 대상에게만, 필요한 포트만, 예상한 존을 통해 열려 있는가”입니다. 저는 검증을 네 층으로 나눕니다. 방화벽 상태, 프로세스 리슨 상태, 출발지별 접속 결과, 운영 문서와의 일치 여부입니다. 이 네 개가 맞아야 비로소 설정이 끝난 겁니다.

    1. 서버 내부에서 활성 Zone과 허용 목록을 확인합니다.
    2. 프로세스가 실제 어떤 주소/포트에 리슨하는지 확인합니다.
    3. 허용된 네트워크와 비허용 네트워크에서 각각 접속 테스트를 합니다.
    4. 문서화된 서비스 목록과 실제 오픈 포트가 일치하는지 봅니다.
    sudo firewall-cmd --get-active-zones
    sudo firewall-cmd --zone=public --list-all
    sudo firewall-cmd --zone=management --list-all
    sudo ss -tulpn

    외부 테스트는 같은 서버 안이 아니라 관리 PC, 점프 호스트, VPN 외부 클라이언트처럼 서로 다른 출발지에서 해보는 게 좋습니다. 예를 들어 HTTPS는 어디서나 접속되어야 하지만, SSH는 허용된 관리 IP에서만 붙어야 합니다. 허용되지 않은 네트워크에서 SSH가 붙는다면 Rich Rule이나 Zone 매핑이 기대와 다르게 동작하는 겁니다. 반대로 HTTPS가 내부에서만 되고 외부에서 안 되면 firewalld보다 앞단 LB, 보안 그룹, 라우팅까지 같이 봐야 합니다.

    검증 결과 해석 기준도 애매하게 두지 않는 편이 좋습니다.

    • list-all에 문서화하지 않은 서비스나 포트가 보이면 정리 대상입니다.
    • public Zone에 SSH, Cockpit, DB 포트가 남아 있으면 노출 과다로 봅니다.
    • ss -tulpn에서 외부 바인딩된 포트가 의도보다 많으면 앱 설정부터 줄여야 합니다.
    • 허용 대상이 아닌 출발지에서도 접속되면 Zone 매핑, Rich Rule, 앞단 네트워크 정책을 다시 확인합니다.
    • 재부팅 후 결과가 바뀌면 영구 설정 관리에 구멍이 있는 겁니다.

    실무에서는 “포트가 열렸는가”보다 “왜 열려 있는가”를 물어야 합니다. 이 질문이 빠지면 방화벽은 언젠가 예외 규칙 창고가 됩니다. 관련 글로 SELinux, SSH 하드닝, Nginx TLS 점검 가이드도 내부 링크로 함께 묶어두면 검색 유입과 체류 시간 관리에 꽤 도움이 됩니다.

    firewalld 보안 체크리스트 검증 결과를 보여주는 서버 보안 강화 이미지

    허용된 서비스만 남고 관리 포트는 제한된 상태를 검증하는 결과 중심의 대시보드/터미널 이미지입니다.

    8. 운영하면서 유지보수할 때 체크할 항목

    방화벽은 한 번 잠갔다고 끝나는 장치가 아닙니다. 서비스가 늘고 담당자가 바뀌고, 장애 대응 중 임시 예외가 생기면 규칙은 반드시 불어납니다. 그래서 저는 월간 점검이나 배포 체크리스트에 firewalld 보안 체크리스트 항목을 따로 넣습니다. 보안을 강화하는 가장 싼 방법은 새로운 솔루션을 들이는 게 아니라, 이미 열린 예외를 줄이는 겁니다.

    • 사용하지 않는 서비스 제거: 예전에 열었던 포트가 아직 실제 트래픽을 받는지 확인합니다.
    • 출발지 제한 재검토: 퇴역한 사무실 IP, 종료된 VPN 대역이 남아 있지 않은지 봅니다.
    • Zone-인터페이스 매핑 확인: NIC 추가, 이름 변경, 가상 인터페이스 생성 후 매핑이 흐트러지지 않았는지 확인합니다.
    • 앱 배포 문서 동기화: 애플리케이션 포트 변경이 방화벽 정책과 함께 업데이트됐는지 맞춰봅니다.
    • reload 후 재검증: 설정 반영 뒤 실제 접속 테스트까지 끝내야 점검 완료로 봅니다.
    • 커스텀 서비스 정리: /etc/firewalld/services/ 아래 정의가 현재 운영과 맞는지 점검합니다.

    작은 팀일수록 문서화가 귀찮게 느껴질 수 있는데, 방화벽은 문서화하지 않으면 팀이 바뀌는 순간 바로 리스크가 됩니다. 특히 포트를 숫자로만 열어둔 환경은 인수인계 비용이 큽니다. 저는 장기 운영 서비스라면 “서비스명, 포트, 출발지, 이유” 네 가지는 최소한 남겨두는 편이 좋다고 봅니다. 이거 해두면 나중에 진짜 편합니다.

    운영 중 반복 점검해야 할 항목을 한 장으로 압축한 요약 인포그래픽 이미지입니다.

    9. 자주 묻는 질문과 바로 적용할 추천안

    9-1. SSH는 아예 닫는 게 맞을까요?

    콘솔 대체 수단이 안정적으로 있고, 배포 자동화와 장애 복구 루틴이 충분히 성숙했다면 가능합니다. 하지만 대부분의 서버 운영에서는 완전 차단보다 출발지 제한이 더 현실적입니다. 보안과 복구 시간을 같이 봐야 하니까요. 제 추천은 단순합니다. 인터넷 전체 공개는 피하고, 고정 IP 또는 VPN 대역으로 묶으세요.

    9-2. 서비스 추가와 포트 추가 중 뭐가 더 좋나요?

    표준 포트면 서비스가 낫고, 커스텀 앱이면 포트가 빠릅니다. 다만 커스텀 포트가 장기 운영 대상이라면 서비스 XML로 승격하거나 최소한 운영 문서에 의미를 남기세요. “지금 빨리 열기”와 “나중에 이해 가능하기”는 다른 문제입니다.

    9-3. 서버 보안 강화를 시작하는 최소 기준은?

    제가 운영 서버에서 최소선으로 잡는 기준은 이렇습니다. 외부 공개는 HTTPS 중심, SSH는 특정 출발지 제한, 관리망은 공개망과 분리, 불필요한 기본 서비스 제거, reload 후 외부 검증. 이 다섯 개만 지켜도 방화벽 상태는 훨씬 예측 가능해집니다.

    환경별로 추천을 딱 정리해보면 이렇습니다. 단일 NIC 소형 서버라면 public Zone 하나 + HTTPS 공개 + SSH Rich Rule 제한 조합이 가장 실용적입니다. 규칙이 단순하고 운영자가 바뀌어도 이해가 쉽습니다. 반대로 NIC가 둘 이상이거나 관리망이 분리된 서버라면 Zone 분리를 바로 적용하는 편이 낫습니다. public에는 서비스 트래픽만, management에는 관리 트래픽만 남기세요. 이 구조가 예외 규칙 누적을 가장 잘 막아줍니다.

    한 문장으로 요약하면, firewalld 보안 체크리스트의 핵심은 “포트를 여는 기술”이 아니라 “열린 이유를 끝까지 설명할 수 있는 상태를 유지하는 것”입니다. 저는 방화벽을 잘 짠 서버가 보안적으로만 좋은 게 아니라 운영도 덜 아프다고 봅니다. 필요한 것만 열고, 경계를 분리하고, 재부팅 후에도 같은 결과가 나오게 만들고, 마지막엔 실제 접속으로 확인하세요. 이 네 가지를 습관으로 만들면 리눅스 서버는 눈에 띄게 단단해집니다.

  • [보안] 리눅스 서버 하드닝 전략 재점검 체크리스트

    [보안] 리눅스 서버 하드닝 전략 재점검 체크리스트

    리눅스 서버 하드닝 전략 재점검 체크리스트

    요즘 보안 메일함 열어보면 심장이 조금 빨리 뛰죠. 특히 리눅스 서버 하드닝 관점에서 보면, 커널과 사용자 공간(User space, 커널 밖에서 도는 일반 프로세스) 취약점 공지가 꽤 자주 올라옵니다. 저도 홈랩이랑 운영 서버를 같이 굴리다 보니, 처음엔 “요즘 왜 이렇게 Linux CVE가 많아졌지?” 싶었거든요. 그런데 실제로 뜯어보니, 단순히 숫자만 늘었다기보다 공개 체계가 더 촘촘해진 부분, 그리고 실제로 빨리 패치해야 하는 취약점이 섞여 있더라고요.

    특히 2024년 2월 13일에 kernel.org가 CVE Numbering Authority(CNA, CVE 번호 발급 권한 기관)로 추가된 뒤부터는 Linux 커널 취약점에 CVE가 더 체계적으로 붙기 시작했습니다. 거기에 CISA가 실제 악용 사례가 있는 Linux kernel 취약점들을 Known Exploited Vulnerabilities(KEV) 목록에 올리면서, “아, 이건 숫자 구경만 할 게 아니구나” 싶었거든요. 리눅스 보안은 결국 패치 속도만의 문제가 아니라, 노출면(Attack Surface, 공격 가능 면적)을 줄이는 운영 습관의 문제이기도 합니다.

    리눅스 서버 하드닝 전체 구조를 보여주는 아키텍처 이미지

    커널, SSH, 방화벽, MAC 정책, 패치 자동화, 검증 흐름까지 한눈에 보여주는 개요 이미지입니다.

    1. 최근 Linux CVE가 많이 보이는 이유부터 정리해보겠습니다

    쉽게 말해, 요즘 보이는 “급증”은 두 가지가 섞여 있습니다. 발견과 공개가 더 적극적이 된 점, 그리고 실제 운영자가 체감할 정도로 대응 압박이 커진 점이 같이 온 겁니다.

    구분 무슨 뜻인가 운영자가 봐야 할 포인트
    CVE 공개 체계 변화 kernel.org가 2024-02-13부터 CNA 역할 수행 이전보다 커널 취약점이 더 잘 드러날 수 있음
    실제 악용 사례 CISA가 Linux kernel CVE를 KEV에 추가 패치 우선순위를 높게 잡아야 함
    자동화된 버그 탐지 fuzzing과 정적/동적 분석이 더 활발 작은 버그도 더 빨리 공론화됨

    여기서 중요한 포인트가 있습니다. CVE 숫자가 늘었다 = 리눅스가 갑자기 위험해졌다로 바로 연결하면 좀 단순합니다. 제가 직접 홈랩 커널 업데이트 흐름을 따라가 보니까, 예전엔 배포판 공지 뒤에 숨어 지나가던 수정이 이제는 CVE로 더 명확하게 드러나는 경우도 꽤 있더라고요. 반대로, KEV에 들어간 건 진짜 우선순위가 다릅니다. 이건 “나중에 점검”이 아니라 즉시 확인 쪽입니다.

    2. 리눅스 서버 하드닝 체크리스트, 우선순위는 이렇게 잡으시면 됩니다

    저는 보통 패치, 접근 통제, 권한 축소, 관측성 순으로 봅니다. 이유는 간단하거든요. 멋진 보안 솔루션보다 먼저, 뚫릴 구멍을 줄이는 게 훨씬 싸고 빠릅니다.

    1. 커널과 패키지 업데이트 기준선 만들기: 지금 어떤 커널을 쓰는지, 보안 업데이트가 몇 건 밀렸는지 숫자로 확인합니다.
    2. SSH 노출면 줄이기: 비밀번호 로그인, root 직접 로그인, 불필요한 포트 노출을 먼저 줄입니다.
    3. 방화벽 기본 정책 정리: 허용 목록(Allowlist, 허용 대상만 열기) 방식으로 바꿉니다.
    4. SELinux/AppArmor 같은 MAC 적용: 뚫려도 옆으로 번지는 걸 막습니다.
    5. systemd sandboxing: 서비스 단위로 권한을 더 잘게 자릅니다.
    6. 감사 로그와 검증 루틴: 적용 후 확인하지 않으면 하드닝이 아니라 기분만 좋아지는 설정이 됩니다.

    3. 실전 구현 1단계: 패치 상태와 커널 노출 범위부터 확인합니다

    처음엔 이게 뭔가 싶었는데, 결국 출발점은 단순합니다. 무엇이 돌아가고 있는지 알아야 CVE 대응도 빠릅니다. 아래 명령은 제가 새 서버 잡으면 거의 습관처럼 먼저 칩니다.

    uname -r
    cat /etc/os-release
    ss -tulpn
    systemctl --failed
    

    uname -r은 현재 커널 버전, ss -tulpn은 외부에 열려 있는 포트와 프로세스를 같이 보여줍니다. 여기서 예상 밖 포트가 보이면, 그 서버는 이미 서버 보안 강화 대상입니다.

    Debian/Ubuntu 계열이면 보안 업데이트 확인은 이렇게 시작하시면 됩니다.

    sudo apt update
    apt list --upgradable
    sudo unattended-upgrades --dry-run -d
    

    RHEL 계열은 보통 이렇게 봅니다.

    sudo dnf check-update
    sudo dnf updateinfo list security
    

    Ubuntu를 쓰신다면 Livepatch(재부팅 없이 일부 커널 보안 수정 적용)도 검토할 만합니다. 다만 Canonical 문서 기준으로 Livepatch만 믿고 장기간 재부팅을 미루면 안 됩니다. 지원되는 커널 ABI 범위와 재부팅 주기가 따로 있거든요. 실제로 써보니까 편하기는 한데, 이것도 결국 정식 커널 교체를 완벽히 대체할 수는 없더라고요.

    4. 실전 구현 2단계: SSH와 네트워크부터 조입니다

    제가 제일 먼저 손대는 부분입니다. CVE가 터졌을 때 제일 아쉬운 서버가 어떤 서버냐 하면, 패치가 늦은 서버보다도 불필요하게 다 열어둔 서버더라고요. 삽질 좀 했습니다 ㅎㅎ

    sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%F)
    sudoedit /etc/ssh/sshd_config
    
    PermitRootLogin no
    PasswordAuthentication no
    KbdInteractiveAuthentication no
    PubkeyAuthentication yes
    MaxAuthTries 3
    AllowUsers adminops
    

    적용 전에는 반드시 문법 검사를 하세요.

    sudo sshd -t && sudo systemctl reload sshd
    

    방화벽은 nftables 기준으로 아주 단순하게 시작하는 편이 좋습니다.

    sudo mkdir -p /etc/nftables
    sudoedit /etc/nftables.conf
    
    #!/usr/sbin/nft -f
    flush ruleset
    
    table inet filter {
      chain input {
        type filter hook input priority 0;
        policy drop;
        iif lo accept
        ct state established,related accept
        tcp dport { 22, 80, 443 } accept
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept
      }
    
      chain forward {
        type filter hook forward priority 0;
        policy drop;
      }
    
      chain output {
        type filter hook output priority 0;
        policy accept;
      }
    }
    
    sudo nft -f /etc/nftables.conf
    sudo nft list ruleset
    sudo systemctl enable --now nftables
    
    리눅스 서버 하드닝에서 SSH와 방화벽 정책을 설명하는 이미지

    SSH 접근 제한, 허용 포트 최소화, 상태 기반 방화벽 정책을 시각적으로 보여주는 이미지입니다.

    5. 실전 구현 3단계: SELinux, AppArmor, systemd sandboxing으로 한 번 더 막습니다

    Mandatory Access Control(MAC, 강제 접근 통제)은 초반엔 귀찮아 보여도, 사고 났을 때 진짜 값어치를 합니다. Red Hat 문서도 SELinux를 추가 보안 계층으로 설명하고 있고, Ubuntu 문서도 AppArmor를 경로 기반 MAC으로 안내합니다. 쉽게 말해 프로세스가 원래 해야 할 일만 하게 만드는 장치입니다.

    RHEL 계열에서는 SELinux 상태를 먼저 확인하세요.

    getenforce
    sestatus
    sudo setenforce 1
    

    Ubuntu 계열에서는 AppArmor 상태를 확인합니다.

    sudo apparmor_status
    sudo aa-enforce /etc/apparmor.d/*
    

    그리고 요즘 제가 자주 같이 보는 게 systemd-analyze security입니다. 서비스별 노출 점수를 빠르게 볼 수 있어서, 감으로 하드닝하는 느낌이 훨씬 줄어들더라고요.

    sudo systemd-analyze security nginx.service
    

    서비스 오버라이드(override)로 샌드박싱을 추가하는 예시는 아래처럼 시작하시면 됩니다.

    sudo systemctl edit nginx.service
    
    [Service]
    NoNewPrivileges=yes
    PrivateTmp=yes
    ProtectSystem=strict
    ProtectHome=yes
    PrivateDevices=yes
    RestrictSUIDSGID=yes
    LockPersonality=yes
    MemoryDenyWriteExecute=yes
    RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
    SystemCallArchitectures=native
    
    sudo systemctl daemon-reload
    sudo systemctl restart nginx
    sudo systemd-analyze security nginx.service
    

    이거 실제로 써보니까 꽤 좋더라고요. 다만 여기서 중요한 건, 서비스별로 필요한 권한이 다르다는 점입니다. DB나 백업 에이전트는 너무 세게 조이면 바로 깨집니다.

    6. 커널 노출면 줄이는 sysctl과 감사 로그도 같이 가야 합니다

    리눅스 서버 하드닝에서 빠지기 쉬운 게 커널 파라미터와 로그입니다. 공격 자체를 막는 것도 중요하지만, 이상 징후를 빨리 보는 것도 중요하거든요.

    sudoedit /etc/sysctl.d/99-hardening.conf
    
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.default.accept_redirects = 0
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.default.send_redirects = 0
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.conf.default.rp_filter = 1
    net.ipv4.tcp_syncookies = 1
    kernel.kptr_restrict = 2
    kernel.dmesg_restrict = 1
    fs.protected_symlinks = 1
    fs.protected_hardlinks = 1
    
    sudo sysctl --system
    

    감사 로그(audit log)도 최소한은 잡아두는 편이 좋습니다.

    sudo apt install auditd -y || sudo dnf install audit -y
    sudo systemctl enable --now auditd
    
    sudo auditctl -w /etc/ssh/sshd_config -p wa -k ssh_config_change
    sudo auditctl -w /etc/sudoers -p wa -k sudoers_change
    sudo auditctl -w /usr/bin/sudo -p x -k sudo_exec
    

    여기서 정말 중요한 포인트예요. 로그는 쌓는 것보다 어떤 이벤트를 보면 위험 신호로 판단할지가 더 중요합니다. SSH 설정 변경, sudo 실행 급증, 예상하지 못한 서비스 재시작 같은 건 바로 알아채야 하거든요.

    리눅스 서버 하드닝에서 SELinux AppArmor 시스템 샌드박싱을 보여주는 이미지

    프로세스 권한 최소화와 서비스 격리를 여러 층으로 적용하는 장면을 표현한 이미지입니다.

    7. ⚠️ 실제로 자주 겪는 문제와 트러블슈팅

    하드닝은 설정 넣는 순간보다, 그 뒤에 안 깨지게 만드는 과정이 더 어렵습니다. 저도 처음엔 자신 있게 적용했다가 웹 서비스가 파일 못 읽어서 502 띄운 적 있습니다.

    • SELinux/AppArmor 적용 후 서비스 장애: 먼저 비활성화하지 말고 로그를 봅니다. SELinux는 /var/log/audit/audit.log, AppArmor는 journalctl -xe나 /var/log/syslog 쪽부터 확인하세요.
    • systemd sandboxing 후 서비스 기동 실패: ProtectSystem=strict, ProtectHome=yes가 자주 원인입니다. 서비스가 실제로 써야 하는 경로를 ReadWritePaths=로 열어줘야 할 수 있습니다.
    • SSH 설정 변경 후 원격 접속 끊김: 운영 중인 세션을 유지한 상태에서 새 세션으로 테스트하고, 가능하면 콘솔 접근 수단을 남겨두세요.
    • 방화벽 적용 후 내부 통신 장애: 모니터링, 백업, 노드 간 헬스체크 포트를 빼먹는 경우가 많습니다. 홈랩에서 Kubernetes 올릴 때 저도 이걸로 반나절 썼습니다.

    그래서 저는 항상 한 번에 다 바꾸지 않고, 서비스 하나씩 묶어서 적용합니다. 보안 체크리스트는 체크만 하면 끝나는 문서가 아니라, 배포 순서를 정리하는 운영 문서여야 하더라고요.

    8. 검증, 결과 확인, 그리고 다음 단계

    설정이 들어갔으면 반드시 검증해야 합니다. 아래 정도는 최소 루틴으로 가져가면 좋습니다.

    1. 커널/패키지 버전 확인: 패치 적용 전후 버전 차이를 기록합니다.
    2. 포트 노출 재검사: ss -tulpn 결과를 저장해 비교합니다.
    3. 서비스 샌드박스 점검: systemd-analyze security로 전후 점수를 비교합니다.
    4. 로그 확인: SELinux/AppArmor 거부 로그, audit 로그, 인증 로그를 같이 봅니다.
    5. 재부팅 리허설: 커널 업데이트 후 서비스가 정상 복구되는지 꼭 확인합니다.
    uname -r
    ss -tulpn
    sudo systemd-analyze security
    sudo journalctl -p warning -b
    sudo ausearch -k ssh_config_change
    

    제가 직접 해보니 가장 효과가 큰 건 화려한 솔루션이 아니라 패치 기준선 + SSH 제한 + 방화벽 기본 거부 + MAC + 서비스 샌드박스 이 5개였습니다. 드디어 됐다! 싶은 순간이 오긴 오는데, 사실 그 뒤가 더 중요합니다. 운영 환경은 계속 바뀌니까요.

    리눅스 서버 하드닝 적용 전후 결과를 보여주는 검증 이미지

    적용 전후 비교 결과를 대시보드 형태로 보여주는 검증 이미지입니다.

    정리: 지금 바로 다시 볼 체크포인트

    • 리눅스 서버 하드닝은 CVE 개수보다 노출면 축소가 핵심입니다.
    • kernel.org CNA 전환과 실제 악용 사례 공개를 보면, 공개량 증가와 실제 위험 신호를 구분해서 봐야 합니다.
    • CVE 대응은 패치만이 아니라 SSH, nftables, SELinux/AppArmor, systemd sandboxing까지 묶어서 봐야 합니다.
    • 검증 없는 하드닝은 절반짜리입니다. 적용 후 로그와 서비스 상태를 꼭 확인하세요.

    다음 글에서는 systemd 서비스별 하드닝 템플릿을 좀 더 깊게 다뤄볼 예정입니다. 이전 글에서 다뤘던 로그 수집 파이프라인이 있으시면 그쪽과 묶어서 보셔도 좋고요.

    패치, 접근 통제, MAC, 샌드박스, 로그 검증을 우선순위별로 정리한 요약 이미지입니다.

    참고한 공개 자료