운영 중인 Windows Server 2026 환경에서 핵심 애플리케이션의 서비스가 서버 재부팅 후 자동으로 시작되지 않거나, 시작되더라도 다른 의존성 서비스가 준비되지 않아 비정상 종료되는 문제가 빈번하게 발생했다. 특히 대규모 업데이트나 패치 적용 후 재부팅 시 이러한 현상이 두드러졌으며, 수동으로 서비스를 재시작해야 하는 번거로움과 서비스 중단 시간이 길어지는 문제로 이어졌다.
이러한 현상은 단순히 서비스의 시작 유형을 '자동'으로 설정하는 것만으로는 해결되지 않는다. Windows 서비스 관리자(SCM, Service Control Manager)가 서비스를 시작하는 방식과 서비스 간의 복잡한 의존성 관계, 그리고 시스템 리소스 초기화 순서가 얽혀 있기 때문이다. 특히, 네트워크 서비스나 데이터베이스 서비스처럼 초기화에 시간이 오래 걸리는 인프라 서비스에 애플리케이션 서비스가 강하게 의존하는 경우, 이러한 시작 지연 문제는 더욱 심화된다. 우리는 이 문제의 근본 원인을 파악하고, 안정적인 서비스 시작을 위한 실질적인 해결책을 모색해야 했다.
Windows 서비스 시작 메커니즘과 의존성 문제 분석
Windows 운영체제에서 서비스는 부팅 과정 중 SCM에 의해 관리되고 시작된다. 서비스는 '자동(Automatic)', '자동(지연 시작)(Automatic (Delayed Start))', '수동(Manual)', '사용 안 함(Disabled)' 등의 시작 유형을 가질 수 있다. '자동' 서비스는 시스템 부팅 시 즉시 시작을 시도하지만, 모든 시스템 리소스가 완전히 준비되지 않은 상태에서 시작될 수 있다. 반면 '자동(지연 시작)'은 시스템 부팅 후 다른 '자동' 서비스들이 시작되고 시스템 리소스가 안정화된 후에 시작되도록 설계되어 있다.
우리의 경우, 핵심 애플리케이션 서비스가 '자동'으로 설정되어 있었음에도 불구하고, 이 서비스가 의존하는 데이터베이스 서비스(예: SQL Server)나 특정 네트워크 드라이브 마운트 서비스가 아직 완전히 준비되지 않은 상태에서 시작을 시도하다가 실패하는 패턴을 보였다. 이는 SCM이 서비스 의존성을 관리하지만, 해당 의존성 서비스가 '정상적으로 시작됨' 상태가 되는 시점과 실제 애플리케이션이 사용할 준비가 되는 시점 사이에 간극이 존재하기 때문이다. 즉, 서비스 A가 서비스 B에 의존한다고 선언되어 있고, SCM이 서비스 B를 먼저 시작시킨 후 A를 시작하더라도, B가 실제 요청을 처리할 준비가 되기 전에 A가 B의 리소스를 사용하려다 실패할 수 있는 것이다.
Windows 서비스 의존성은 sc.exe config 명령의 depend= 옵션이나 서비스 속성에서 설정할 수 있다. 하지만 이 의존성은 SCM에게 '시작 순서'를 지시할 뿐, 의존성 서비스의 '완전한 준비 상태'를 보장하지는 않는다. 특히 TCP/IP 포트 바인딩, 파일 시스템 마운트, 외부 API 연결 등은 서비스 시작 후 추가적인 초기화 시간이 필요하다.
서비스 시작 실패 로그 분석 및 진단
서비스 시작 실패의 원인을 정확히 파악하기 위해 시스템 이벤트 로그를 면밀히 분석했다. '이벤트 뷰어(Event Viewer)'의 'Windows 로그' -> '시스템' 및 '애플리케이션' 섹션에서 관련 오류를 찾아볼 수 있었다.
일반적으로 다음과 같은 패턴의 오류 메시지를 발견할 수 있다.
로그 이름: System
원본: Service Control Manager
날짜: 2026-10-15 오전 9:02:17
이벤트 ID: 7000
작업 범주: 없음
수준: 오류
키워드: 클래식
사용자: N/A
컴퓨터: WIN-SERVER-APP01
설명:
MyApplicationService 서비스가 다음 오류 때문에 시작하지 못했습니다.
종속 서비스 또는 그룹이 시작되지 못했습니다.
로그 이름: Application
원본: .NET Runtime
날짜: 2026-10-15 오전 9:02:20
이벤트 ID: 1026
작업 범주: 없음
수준: 오류
키워드: 클래식
사용자: N/A
컴퓨터: WIN-SERVER-APP01
설명:
애플리케이션: MyApplication.exe
프레임워크 버전: v4.0.30319
설명: 프로세스가 종료되었습니다.
예외 정보: System.Data.SqlClient.SqlException
at System.Data.SqlClient.SqlInternalConnectionTds..ctor(System.Data.ProviderBase.DbConnectionPoolIdentity, System.Data.SqlClient.SqlConnectionString, System.Data.SqlClient.SqlCredential, System.Object, System.String, System.Security.SecureString, System.Boolean, System.Data.SqlClient.SqlReferenceCollection, System.Data.SqlClient.SqlConnection, System.Boolean)
... (중략) ...
SQL Server에 연결할 수 없습니다. 서버를 찾을 수 없거나 액세스할 수 없습니다.
위 로그는 MyApplicationService가 시작되지 못했으며, 그 원인이 '종속 서비스 또는 그룹이 시작되지 못했기 때문'임을 명확히 보여준다. 이어서 애플리케이션 로그에서는 MyApplication.exe가 SQL Server에 연결할 수 없어 종료되었다는 예외를 기록하고 있다. 이는 MyApplicationService가 SQL Server 서비스에 의존하는데, SCM이 SQL Server 서비스를 시작했다고 판단한 시점과 실제 SQL Server가 클라이언트 연결을 수락할 준비가 된 시점 사이에 불일치가 있었음을 시사한다.
서비스 시작 유형 및 의존성 설정 최적화
이러한 문제를 해결하기 위한 첫 번째 단계는 서비스 시작 유형과 명시적 의존성을 올바르게 설정하는 것이다.
1. 서비스 시작 유형 변경
핵심 애플리케이션 서비스의 시작 유형을 '자동(지연 시작)'으로 변경하여, 시스템 부팅 후 다른 필수 서비스들이 충분히 초기화될 시간을 확보한다.
services.msc를 실행하여 서비스 관리자 콘솔을 연다.- 대상 서비스(
MyApplicationService)를 찾아 마우스 오른쪽 버튼을 클릭하고 '속성'을 선택한다. - '일반' 탭에서 '시작 유형(Startup type)'을 '자동(지연 시작)(Automatic (Delayed Start))'으로 변경한다.
- '확인'을 클릭하여 설정을 저장한다.
또는 sc.exe 명령어를 사용하여 명령 프롬프트에서 변경할 수도 있다.
# MyApplicationService의 시작 유형을 '자동(지연 시작)'으로 변경
sc.exe config MyApplicationService start= delayed-auto
[SC] ChangeServiceConfig SUCCESS
2. 명시적 서비스 의존성 추가
MyApplicationService가 SQLSERVER 서비스에 의존한다는 것을 SCM에 명시적으로 알려준다.
- 명령 프롬프트를 관리자 권한으로 실행한다.
sc.exe config명령어를 사용하여 의존성을 추가한다. 여기서depend=뒤에 의존하는 서비스의 '서비스 이름'을 입력한다. 여러 서비스에 의존하는 경우 슬래시(/)로 구분한다.
# MyApplicationService가 SQLSERVER 서비스에 의존하도록 설정
# SQLSERVER는 SQL Server 인스턴스의 실제 서비스 이름으로 대체해야 함 (예: MSSQLSERVER)
sc.exe config MyApplicationService depend= MSSQLSERVER
[SC] ChangeServiceConfig SUCCESS
참고: MSSQLSERVER는 SQL Server 기본 인스턴스의 서비스 이름이다. 명명된 인스턴스(Named Instance)의 경우 MSSQL$INSTANCENAME 형태가 된다. 정확한 서비스 이름은 services.msc에서 확인할 수 있다.
서비스 시작 후 지연 및 재시도 로직 구현
'자동(지연 시작)'과 명시적 의존성 설정만으로는 의존성 서비스가 '실제 사용 가능' 상태가 되는 시점을 완벽하게 보장하기 어렵다. 특히 외부 네트워크 리소스나 대용량 데이터베이스 초기화에 시간이 더 필요한 경우 더욱 그렇다. 이 문제를 해결하기 위해 애플리케이션 서비스 자체에 '시작 후 지연' 및 '재시도' 로직을 구현하는 것이 가장 견고한 방법이다.
애플리케이션 서비스가 시작될 때, 의존하는 서비스(예: 데이터베이스)에 대한 연결을 즉시 시도하는 대신, 일정 시간 대기한 후 연결을 시도하고, 실패 시 여러 번 재시도하는 로직을 추가한다.
예를 들어, C# .NET 서비스의 경우 Program.cs 또는 서비스 시작 로직에 다음과 같은 코드를 추가할 수 있다.
// C# .NET Core/Framework 서비스 시작 로직 예시
using System;
using System.Threading;
using System.Data.SqlClient;
public class MyApplicationService
{
private const int MaxRetryAttempts = 10;
private const int RetryDelayMilliseconds = 5000; // 5초 대기
public void Start()
{
Console.WriteLine("MyApplicationService 시작 중...");
// 초기 지연 시간 (선택 사항, '자동(지연 시작)'과 함께 사용 시 더욱 안정적)
Thread.Sleep(TimeSpan.FromSeconds(10));
for (int i = 0; i < MaxRetryAttempts; i++)
{
try
{
Console.WriteLine($"데이터베이스 연결 시도 중... (시도 {i + 1}/{MaxRetryAttempts})");
// 실제 데이터베이스 연결 로직
using (var connection = new SqlConnection("YourConnectionString"))
{
connection.Open();
Console.WriteLine("데이터베이스 연결 성공!");
// 연결 성공 후 서비스의 핵심 로직 시작
StartCoreServiceLogic();
return; // 성공했으므로 루프 종료
}
}
catch (SqlException ex)
{
Console.WriteLine($"데이터베이스 연결 실패: {ex.Message}");
if (i < MaxRetryAttempts - 1)
{
Console.WriteLine($"{RetryDelayMilliseconds / 1000}초 후 재시도...");
Thread.Sleep(RetryDelayMilliseconds);
}
else
{
Console.WriteLine("최대 재시도 횟수 초과. 서비스 시작 실패.");
throw; // 모든 재시도 실패 시 예외 발생
}
}
}
}
private void StartCoreServiceLogic()
{
Console.WriteLine("핵심 서비스 로직 시작.");
// 여기에 실제 서비스의 주요 작업을 구현
}
public void Stop()
{
Console.WriteLine("MyApplicationService 중지 중...");
// 서비스 중지 로직
}
}
이러한 로직은 서비스가 시작된 후에도 의존성 서비스가 완전히 준비될 때까지 유연하게 대기하고 재시도함으로써, 견고성을 크게 향상시킨다.
Windows 서비스 복구 옵션 구성
서비스가 예기치 않게 종료되었을 때 자동으로 재시작되도록 Windows 서비스 복구 옵션을 구성하는 것도 중요하다. 이는 일시적인 의존성 문제나 리소스 부족으로 인한 단기적인 서비스 중단에 대비하는 효과적인 방법이다.
services.msc를 실행하여 서비스 관리자 콘솔을 연다.- 대상 서비스(
MyApplicationService)를 찾아 마우스 오른쪽 버튼을 클릭하고 '속성'을 선택한다. - '복구(Recovery)' 탭으로 이동한다.
- '처음 실패 시(First failure)', '두 번째 실패 시(Second failure)', '이후 실패 시(Subsequent failures)' 옵션을 '서비스 다시 시작(Restart the Service)'으로 설정한다.
- '다시 시작 간격(Reset fail count after)'을 적절한 값으로 설정한다 (예: 1일).
- '다시 시작 지연 시간(Restart service after)'을 설정하여 재시작 시 약간의 지연 시간을 준다 (예: 1분). 이는 빠르게 실패-재시작 루프에 빠지는 것을 방지한다.
- '확인'을 클릭하여 설정을 저장한다.
sc.exe 명령어를 사용한 설정은 다음과 같다.
# MyApplicationService 서비스의 복구 옵션 설정
# failure= reset/reboot/run/command/actions
# actions= restart/10000/restart/20000/restart/30000 (첫 실패 시 10초 후 재시작, 두 번째 실패 시 20초 후 재시작, 이후 실패 시 30초 후 재시작)
# reset= 86400 (실패 횟수 1일 후 초기화)
sc.exe failure MyApplicationService reset= 86400 actions= restart/60000/restart/60000/restart/60000
[SC] ChangeServiceConfig2 SUCCESS
서비스 복구 옵션 설정 시 '다시 시작 지연 시간'을 너무 짧게 설정하면, 의존성 문제가 지속될 경우 서비스가 무한 재시작 루프에 빠져 시스템 리소스를 고갈시키거나 이벤트 로그를 과도하게 생성할 수 있다. 충분한 지연 시간을 설정하여 의존성 서비스가 안정화될 시간을 주는 것이 중요하다.
Windows 서브시스템의 네트워크 및 포트 바인딩 지연 해결
Windows Server 환경에서 네트워크 관련 서비스(예: IIS, 특정 미들웨어)는 TCP/IP 스택이 완전히 초기화되고 포트 바인딩이 가능해질 때까지 기다려야 한다. 때로는 네트워크 드라이버 초기화 지연이나 DHCP 주소 할당 지연 등으로 인해 서비스가 필요한 포트를 제때 바인딩하지 못하는 경우가 발생한다.
이러한 문제를 해결하기 위해 다음과 같은 방법을 고려할 수 있다.
1. 네트워크 의존성 명시
서비스가 네트워크에 의존하는 경우, LanmanServer (서버 서비스) 또는 Dnscache (DNS 클라이언트)와 같은 핵심 네트워크 서비스에 대한 의존성을 명시적으로 추가할 수 있다.
# MyApplicationService가 LanmanServer에 의존하도록 설정 (예시)
sc.exe config MyApplicationService depend= LanmanServer/MSSQLSERVER
2. TCP/IP 포트 바인딩 재시도 로직
애플리케이션 서비스가 특정 포트에 바인딩해야 하는 경우, 해당 포트가 사용 가능해질 때까지 주기적으로 재시도하는 로직을 구현한다. 이는 애플리케이션 코드 내에서 구현되어야 한다.
// C# .NET Core/Framework에서 포트 바인딩 재시도 로직 예시
using System.Net.Sockets;
using System.Net;
public void BindPortWithRetry(int port)
{
const int MaxPortBindRetries = 5;
const int PortBindDelayMilliseconds = 2000;
for (int i = 0; i < MaxPortBindRetries; i++)
{
try
{
using (var listener = new TcpListener(IPAddress.Any, port))
{
listener.Start(); // 포트 바인딩 시도
listener.Stop();
Console.WriteLine($"포트 {port} 바인딩 성공!");
return;
}
}
catch (SocketException ex)
{
Console.WriteLine($"포트 {port} 바인딩 실패: {ex.Message}");
if (i < MaxPortBindRetries - 1)
{
Console.WriteLine($"{PortBindDelayMilliseconds / 1000}초 후 재시도...");
Thread.Sleep(PortBindDelayMilliseconds);
}
else
{
Console.WriteLine("최대 포트 바인딩 재시도 횟수 초과. 서비스 시작 실패.");
throw;
}
}
}
}
이러한 로직은 특히 서버 재부팅 시 포트가 일시적으로 사용 불가능하거나, 다른 서비스에 의해 지연될 수 있는 상황에서 유용하다.
- 대상 서비스의 정확한 '서비스 이름' 확인 (
services.msc또는sc.exe query) - 의존성 서비스의 정확한 '서비스 이름' 확인
- 애플리케이션 코드 수정 시 충분한 테스트 환경에서 검증
- 변경 전 기존 서비스 설정 백업 (스크린샷 또는
sc.exe qc명령 결과 저장)
실무 트러블슈팅 FAQ
### Q. '자동(지연 시작)'으로 설정했는데도 여전히 서비스가 제대로 시작되지 않습니다. 다른 원인이 있을까요?
A. '자동(지연 시작)'은 시스템 부팅 후 약 1~2분 정도의 지연 시간을 두어 다른 서비스들이 시작될 시간을 주지만, 이는 절대적인 보장이 아닙니다. 의존하는 서비스가 초기화에 더 많은 시간이 필요하거나, 네트워크 연결, 외부 리소스(예: SAN 스토리지 마운트, Active Directory 연결) 준비에 시간이 오래 걸리는 경우 여전히 문제가 발생할 수 있습니다. 이 경우 애플리케이션 서비스 내부에 재시도 로직을 구현하거나, PowerShell 스크립트를 통해 서비스 시작 전 특정 조건(예: 포트 열림, 파일 존재 여부)을 확인하고 시작하도록 스케줄링하는 방법을 고려해야 합니다.
### Q. 서비스 의존성을 추가했는데, SCM이 "지정된 서비스를 종속성으로 추가할 수 없습니다." 오류를 반환합니다.
A. 이 오류는 주로 의존성으로 지정하려는 서비스 이름이 정확하지 않거나, 해당 서비스가 시스템에 존재하지 않을 때 발생합니다. services.msc를 열어 정확한 '서비스 이름'을 확인해야 합니다. '표시 이름'과 '서비스 이름'은 다를 수 있습니다. 예를 들어, 'SQL Server (MSSQLSERVER)'의 서비스 이름은 MSSQLSERVER입니다. 또한, 순환 의존성(Circular Dependency)이 감지될 때도 이 오류가 발생할 수 있습니다.
### Q. 서비스 시작 스크립트에서 특정 프로그램을 실행해야 하는데, 서비스 계정 권한 문제로 실패합니다.
A. Windows 서비스는 기본적으로 Local System 계정으로 실행되지만, 이 계정은 네트워크 리소스 접근이나 특정 사용자 프로필이 필요한 작업에 제한이 있을 수 있습니다. 서비스가 특정 리소스에 접근해야 한다면, 서비스의 '로그온(Log On)' 탭에서 도메인 사용자 계정 또는 로컬 사용자 계정을 지정해야 합니다. 해당 계정에는 필요한 파일 시스템 권한, 네트워크 권한, 레지스트리 권한 등이 충분히 부여되어야 합니다. 또한, 서비스 계정에 '서비스로 로그온' 권한이 부여되어 있는지 확인해야 합니다.
### Q. 서비스 시작 시 "오류 1053: 서비스가 시작 요청이나 제어 요청에 제때 응답하지 않았습니다." 메시지가 발생합니다.
A. 이 오류는 서비스가 SCM에 의해 시작된 후, 설정된 시간(기본값 30초) 내에 SCM에 '시작됨' 신호를 보내지 못했을 때 발생합니다. 이는 서비스의 초기화 로직이 너무 복잡하거나, 의존성 리소스를 기다리느라 시간이 지연될 때 흔히 나타납니다. 해결책으로는 서비스 내부의 초기화 로직을 최적화하거나, SCM의 서비스 타임아웃 값을 늘리는 방법이 있습니다. 레지스트리 편집기(regedit)에서 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control 경로에 ServicesPipeTimeout이라는 DWORD 값을 추가하고, 원하는 타임아웃 시간(밀리초 단위, 예: 120000 = 2분)을 설정한 후 시스템을 재부팅하면 됩니다.
| 항목 | 설명 | 기본값 |
|---|---|---|
start= auto |
시스템 부팅 시 자동 시작 | - |
start= delayed-auto |
다른 자동 서비스 시작 후 지연 시작 | - |
depend= |
의존하는 서비스의 이름 (슬래시로 구분) |
- |
failure= actions= |
서비스 실패 시 수행할 동작 (재시작, 명령 실행 등) |
- |
failure= reset= |
실패 횟수를 초기화하는 시간 (초 단위) |
86400 (1일) |
ServicesPipeTimeout |
SCM 서비스 시작 타임아웃 (레지스트리) |
30000 (30초) |
