Linux x86-64 환경에서 Go 1.27.2를 사용자별 디렉터리에 설치하고, 기존 시스템 Go를 유지하면서 go-chi/v5로 HTTP 서버를 구성하는 방법을 정리합니다. 설치 확인부터 Go 모듈 설정, 실행, 테스트까지 진행합니다.
Go 1.27.2는 2026년 10월 8일 정식 릴리스되었습니다. 이 글의 Go 버전 및 체크섬은 2026년 10월 16일을 기준으로 확인한 값입니다. 이후 버전을 변경한다면 Go 공식 다운로드 페이지에서 파일명과 체크섬을 다시 확인해야 합니다.
설치 환경과 Go 버전 선택
문서의 기준 환경은 다음과 같습니다. 경로는 제공된 개발 환경을 기준으로 하며, 다른 컴퓨터에서는 계정명과 프로젝트 경로를 변경해야 합니다.
| 항목 | 경로 또는 값 |
|---|---|
| 운영체제 | Linux x86-64 (linux/amd64) |
| 시스템 Go | /usr/local/go |
| 기존 사용자 Go | /home/user/.local/share/go-toolchains/go1.27.1 |
| 설치할 사용자 Go | /home/user/.local/share/go-toolchains/go1.27.2 |
| 프로젝트 | /home/user/projects/onproject |
| 다운로드한 아카이브 | downloads/go1.27.2.linux-amd64.tar.gz |
| 기본 셸 | zsh (~/.zshrc 사용) |
| 툴체인 전환 정책 | GOTOOLCHAIN=local |
현재 사용 중인 Go 확인
uname -m
command -v go
type -a go
go version
go env GOROOT GOPATH GOTOOLCHAIN GOMOD GOWORK
command -v go: 현재 셸에서 우선 선택되는go실행 파일 경로를 표시합니다.type -a go:PATH에서 찾을 수 있는 Go 명령과 셸의 다른 명령 정의를 함께 확인하는 데 유용합니다.go version: 실제 실행에 사용되는 Go 툴체인 버전을 표시합니다.GOROOT: 선택된 Go 배포판의 설치 루트입니다. 사용자 설치 버전을 사용하면 일반적으로 해당 버전 디렉터리입니다.GOPATH: 모듈 다운로드 캐시와 Go 도구 설치 경로 등에 사용되는 작업 영역의 기본 경로입니다. 일반적으로$HOME/go이며GOROOT와는 다릅니다.GOMOD/GOWORK: 현재 Go 모듈 및 워크스페이스 파일을 확인하는 데 사용합니다.
Go 1.21부터 go 명령은 go.mod 또는 go.work에 선언된 버전에 따라 다른 Go 툴체인을 선택할 수 있습니다. 다만 GOTOOLCHAIN=local을 지정하면 현재 실행한 go 명령에 포함된 툴체인만 사용하며, 더 높은 Go 버전이 필요해도 자동으로 다운로드하거나 전환하지 않습니다. 이 설정은 일반적인 외부 모듈 의존성 다운로드까지 차단하는 옵션은 아닙니다.
/usr/local/go와 사용자별go1.27.1설치는 삭제하지 않습니다. 이번 작업은go1.27.2를 별도로 설치하고PATH의 우선순위만 변경합니다.
Go 1.27.2 다운로드 및 사용자별 설치
아카이브와 SHA256 확인
먼저 다운로드 파일이 준비되어 있는지 확인합니다.
cd /home/user/projects/onproject
ls -lh downloads/go1.27.2.linux-amd64.tar.gz
파일이 없다면 다음과 같이 공식 배포 주소에서 내려받습니다.
mkdir -p downloads
curl -fL https://go.dev/dl/go1.27.2.linux-amd64.tar.gz \
-o downloads/go1.27.2.linux-amd64.tar.gz
Go 1.27.2 Linux amd64 아카이브의 공식 SHA256은 다음과 같습니다.
ecbadb99091a3f46e31f5f934b068b1864eafa7995211b39eaddf76996045fe5
아래 명령을 실행하면 직접 계산한 체크섬과 기대값을 비교합니다.
printf '%s %s\n' \
'ecbadb99091a3f46e31f5f934b068b1864eafa7995211b39eaddf76996045fe5' \
'downloads/go1.27.2.linux-amd64.tar.gz' | sha256sum -c -
정상적으로 검증되면 아래와 같이 출력됩니다.
downloads/go1.27.2.linux-amd64.tar.gz: OK
FAILED가 출력된다면 아카이브를 압축 해제하지 말고 파일과 공식 체크섬을 다시 확인합니다. 아키텍처가 aarch64인 시스템에는 이 파일이 아니라 linux-arm64 배포판을 사용해야 합니다.
사용자 디렉터리에 압축 해제
공식 Go 아카이브는 최상위 go/ 디렉터리를 포함합니다. --strip-components=1을 사용하면 버전별 설치 디렉터리 바로 아래에 bin/, pkg/, src/ 등이 배치됩니다.
cd /home/user/projects/onproject
GO_INSTALL_DIR="$HOME/.local/share/go-toolchains/go1.27.2"
mkdir -p "$HOME/.local/share/go-toolchains"
# 새 버전 디렉터리는 비어 있는 상태에서 설치합니다.
mkdir "$GO_INSTALL_DIR"
tar -xzf downloads/go1.27.2.linux-amd64.tar.gz \
-C "$GO_INSTALL_DIR" \
--strip-components=1
"$GO_INSTALL_DIR/bin/go" version
설치 성공 시 마지막 명령의 기대 출력은 다음과 같습니다.
go version go1.27.2 linux/amd64
이미 go1.27.2 디렉터리가 존재하면 mkdir에서 오류가 발생합니다. 이 경우 기존 설치 상태를 먼저 확인하고, 다른 버전의 파일이 섞이지 않도록 기존 디렉터리 위에 압축을 덮어 풀지 않습니다. 설치 중 압축 해제가 실패한 경우에도 해당 디렉터리의 상태를 확인한 후 새 디렉터리에서 다시 설치합니다.
zsh에서 기본 Go 변경
현재 ~/.zshrc에 등록된 Go 경로부터 확인합니다.
grep -nE 'go-toolchains|GOTOOLCHAIN|GOROOT|/usr/local/go' ~/.zshrc
~/.zshrc에 있는 기존 go1.27.1/bin PATH 설정을 대체하여 다음과 같이 지정합니다. 아래 코드는 셸에서 반복 실행하는 명령이 아니라 ~/.zshrc에 저장할 설정 내용입니다.
export PATH="$HOME/.local/share/go-toolchains/go1.27.2/bin:$PATH"
export GOTOOLCHAIN=local
Go 툴체인을 바꿀 때 GOROOT를 오래된 설치 경로로 직접 고정해 두었다면 해당 설정도 제거하거나 적절히 수정해야 합니다. 일반적인 Go 설치에서는 GOROOT를 직접 지정하지 않는 편이 좋습니다.
수정한 설정을 적용합니다.
source ~/.zshrc
rehash
command -v go
go version
go env GOROOT GOTOOLCHAIN GOPATH
기대 결과의 핵심은 다음과 같습니다.
/home/user/.local/share/go-toolchains/go1.27.2/bin/go
go version go1.27.2 linux/amd64
/home/user/.local/share/go-toolchains/go1.27.2
local
/home/user/go
위 예시는 각 확인 명령의 주요 결과를 순서대로 나타낸 것입니다. GOPATH는 사용자 설정에 따라 달라질 수 있습니다. 새 터미널에서도 동일하게 선택되는지 확인하면 설정이 제대로 적용된 것입니다.
Go 모듈과 go-chi 의존성 설정
go-chi는 Go 실행 파일처럼 운영체제에 전역으로 설치하는 프로그램이 아닙니다. github.com/go-chi/chi/v5라는 Go 모듈을 프로젝트의 의존성으로 추가하여 사용합니다. go.mod에는 모듈 경로와 필요한 버전이 기록되고, go.sum에는 모듈 콘텐츠의 검증용 체크섬이 기록됩니다.
프로젝트 초기화와 Go 버전 설정
다음 예시는 신규 실습 프로젝트를 생성하는 경우입니다.
mkdir -p /home/user/projects/onproject/samples/chi-router
cd /home/user/projects/onproject/samples/chi-router
go mod init example.com/chi-router
go mod edit -go=1.27.2
이미 go.mod가 있다면 go mod init을 다시 실행하지 않습니다. 모듈 이름 역시 실제 Git 저장소 경로가 정해져 있다면 그 경로를 사용하는 것이 좋습니다.
go.mod의 go 지시문은 모듈에 요구되는 최소 Go 버전을 나타냅니다. go mod edit -go=1.27.2는 이 지시문을 수정할 뿐, 운영체제에 Go 1.27.2를 설치하거나 PATH를 변경하지 않습니다.
module example.com/chi-router
go 1.27.2
toolchain go1.xx.x 지시문이 있는 모듈은 기본 툴체인에 대해 선호 버전을 제시할 수 있습니다. 하지만 GOTOOLCHAIN=local인 경우에는 그 지시문만으로 툴체인이 자동 전환되지 않습니다. 의존성이 요구하는 최소 Go 버전보다 go 지시문의 버전이 낮아서도 안 됩니다.
go-chi v5 추가
2026년 10월 16일 기준으로 확인한 go-chi 릴리스 v5.3.2를 고정하여 추가합니다.
cd /home/user/projects/onproject/samples/chi-router
go get github.com/go-chi/chi/v5@v5.3.2
특정 시점의 최신 릴리스를 사용하려는 경우에는 다음 명령도 가능합니다.
go get github.com/go-chi/chi/v5@latest
그러나 같은 코드를 반복 빌드할 때 의존성 변경을 통제하려면 확인된 버전을 명시하는 것이 편리합니다. 실제 사용 버전은 다음과 같이 조회합니다.
go list -m github.com/go-chi/chi/v5
아래 명령은 코드 작성 후 실행합니다.
go mod tidy
go mod tidy는 실제 import와 모듈 요구사항을 바탕으로 go.mod, go.sum을 정리합니다. 아직 코드에서 go-chi를 import하지 않았다면 사용하지 않는 직접 의존성을 정리할 수 있으므로 예제 소스 파일을 만든 다음 실행하는 것이 좋습니다.
go-chi HTTP 라우터 실행과 테스트
실행 코드 작성
프로젝트 디렉터리의 main.go를 다음과 같이 작성합니다.
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func newRouter() http.Handler {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Recoverer)
r.Get("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, "Hello, world!")
})
r.Get("/health", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintln(w, "ok")
})
r.Get("/users/{id}", func(w http.ResponseWriter, req *http.Request) {
id := chi.URLParam(req, "id")
fmt.Fprintf(w, "user=%s\n", id)
})
return r
}
func main() {
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: newRouter(),
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Println("HTTP server listening on http://127.0.0.1:8080")
log.Fatal(server.ListenAndServe())
}
chi.NewRouter()는 HTTP 요청을 URL 패턴과 HTTP 메서드에 따라 처리하는 라우터를 생성합니다. r.Get()은 GET 요청을 등록하고, chi.URLParam()은 {id}와 같은 URL 매개변수를 읽습니다. middleware.Recoverer는 요청 처리 중 패닉을 복구하는 데 도움을 줍니다.
이 예제는 로컬 개발 테스트를 위해 127.0.0.1에만 바인딩합니다. 외부 공개 서버에서는 TLS, 접근 통제, 리버스 프록시 구성과 함께 요청 본문 크기 제한 및 정상 종료(graceful shutdown)도 검토해야 합니다. 타임아웃 값은 서비스의 실제 요청 특성에 맞게 조정합니다.
go-chi 5.3 계열에서는 과거
middleware.RealIP대신 프록시 신뢰 구성을 명시하는middleware.ClientIP...계열을 제공하고 있습니다. 프록시 헤더를 검증 없이 클라이언트 IP로 신뢰하면 IP 스푸핑 문제가 생길 수 있으므로, 이 예제에서는 별도의 클라이언트 IP 판별 미들웨어를 추가하지 않았습니다.
테스트 코드 작성
동일한 디렉터리에 main_test.go를 작성합니다. newRouter()를 독립 함수로 구성했으므로 실제 포트를 열지 않고 HTTP 라우터를 테스트할 수 있습니다.
package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHealth(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
recorder := httptest.NewRecorder()
newRouter().ServeHTTP(recorder, req)
if recorder.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
}
if body := strings.TrimSpace(recorder.Body.String()); body != "ok" {
t.Fatalf("body = %q, want %q", body, "ok")
}
}
func TestUserRoute(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/users/123", nil)
recorder := httptest.NewRecorder()
newRouter().ServeHTTP(recorder, req)
if body := strings.TrimSpace(recorder.Body.String()); body != "user=123" {
t.Fatalf("body = %q, want %q", body, "user=123")
}
}
완성된 기본 파일 구조는 아래와 같습니다.
chi-router/
├── go.mod
├── go.sum
├── main.go
└── main_test.go
코드 형식을 정리하고 모듈을 정리한 뒤 테스트합니다.
cd /home/user/projects/onproject/samples/chi-router
go fmt ./...
go mod tidy
go test -v ./...
go vet ./...
테스트가 정상이라면 TestHealth, TestUserRoute가 각각 PASS로 표시됩니다.
서버를 실행합니다.
go run .
다른 터미널에서 요청을 보내 확인합니다.
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/health
curl -i http://127.0.0.1:8080/users/123
응답 본문은 각각 Hello, world!, ok, user=123입니다. 서버 실행을 마치려면 실행 중인 터미널에서 Ctrl+C를 입력합니다.
문제 해결 및 운영 시 확인할 사항
| 증상 | 확인 및 조치 |
|---|---|
go version이 1.27.1로 출력됨 |
command -v go, type -a go로 우선순위를 확인하고 ~/.zshrc의 기존 PATH 줄을 변경한 뒤 새 터미널을 실행합니다. |
새 go 명령을 찾지 못함 |
$HOME/.local/share/go-toolchains/go1.27.2/bin/go의 존재와 실행 권한을 확인합니다. |
go.mod requires go >= ... 오류 |
현재 툴체인보다 모듈 최소 요구 버전이 높습니다. go version, go.mod, GOTOOLCHAIN 설정을 함께 확인합니다. |
checksum FAILED |
다운로드 파일이 손상되었거나 파일/아키텍처/체크섬이 다릅니다. 설치를 진행하지 말고 공식 값을 대조합니다. |
no required module provides package ... |
프로젝트 루트의 go.mod 존재 여부와 go get github.com/go-chi/chi/v5@v5.3.2 실행 여부를 확인합니다. |
undefined: newRouter |
main.go와 main_test.go가 같은 패키지와 디렉터리에 있는지, 함수 이름이 일치하는지 확인합니다. |
go test ./...에서 [no test files] |
해당 패키지에 _test.go 파일이 없다는 안내이며, 그 자체로 테스트 실패가 아닙니다. |
bind: address already in use |
다른 프로세스가 8080 포트를 사용하고 있는지 `ss -lntp |
Go 실행 경로와 툴체인 적용 상태를 한 번에 점검하려면 다음 명령을 사용합니다.
command -v go
go version
go env GOROOT GOPATH GOTOOLCHAIN
grep -E '^(module|go|toolchain|require)' go.mod
go list -m github.com/go-chi/chi/v5
go test ./...
개인 계정으로 go run .을 실행할 때의 환경 설정은 systemd로 등록한 별도 서비스 계정에 자동 적용되지 않습니다. 운영 서버에서는 실제 서비스 실행 계정의 PATH와 Go 설치 경로를 구분하고, 필요하면 빌드한 바이너리의 절대 경로를 ExecStart에 지정하는 방식으로 관리합니다. 컴파일된 일반 Go 서버 바이너리를 실행할 때는 Go 컴파일러 자체가 서버에 반드시 설치되어 있을 필요는 없습니다(단, 애플리케이션의 동적 라이브러리 의존성 등은 별도로 확인해야 합니다).
비공개 Gitea 저장소를 모듈 경로로 사용하는 경우에는 인증 설정 외에도 해당 도메인을 GOPRIVATE에 등록해야 할 수 있습니다. 공개 GitHub 모듈인 go-chi를 사용하는 이번 실습에는 필요하지 않습니다.
참고 자료
'프로그래밍 > Go Notes' 카테고리의 다른 글
| 하나의 프로세스에서 여러 포트로 웹 서버 실행하기 (0) | 2026.07.28 |
|---|
댓글