이번 포스팅에서는 LocalSend 사용법에 대해서 정리하고자 한다. LocalSend는 계정도, 인터넷 연결도, 서드파티 서버도 없이 같은 로컬 네트워크에 있는 기기끼리 파일을 주고받는 오픈소스 도구다. 2026년 7월 기준 GitHub 스타 85.4k, 포크 4.7k를 기록하고 있고 Apache-2.0 라이선스로 배포되며, Android 5.0 이상, iOS 12.0 이상, macOS 11 Big Sur 이상, Windows 10 이상, Linux, Fire OS까지 여섯 개 플랫폼을 한꺼번에 커버한다. 앞부분에서는 갤럭시에서 맥북으로 4GB짜리 영상을 옮기는 것 같은 일상적인 상황을 60초 안에 해결하는 방법을 다루고, 뒷부분에서는 이 앱이 224.0.0.167:53317 멀티캐스트 그룹에 자신을 알리고 /api/localsend/v2/prepare-upload로 세션을 여는 구조를 프로토콜 스펙 v2.1 수준에서 뜯어본다. 마지막에는 최신 안정 릴리스인 v1.17.0이 2025년 2월 20일자, 즉 오늘 기준 약 17개월 전이라는 사실을 포함해 도구의 한계도 함께 짚는다.
에어드랍이 갤럭시 앞에서 멈추는 이유

AirDrop이 갤럭시 앞에서 멈추는 건 성능 문제가 아니라 애플 전용 규격이라서다. 애플 기기끼리는 훌륭하지만 다른 진영에서는 그냥 없는 기능이다. LocalSend는 같은 일을 표준 UDP 멀티캐스트와 HTTP로 다시 구현해 이 벽을 없앤 오픈소스 대체재다. 두 기기가 같은 Wi-Fi에 있기만 하면 앱을 켜는 순간 서로를 발견하고, 파일은 공유기 내부에서만 오간다.
그래서 Windows 노트북과 아이폰, 리눅스 데스크톱과 안드로이드 태블릿 같은 조합은 늘 카카오톡 나에게 보내기나 구글 드라이브를 한 번 거쳐 왔다.
이 우회에는 값이 붙는다. 올렸다가 다시 받아야 하니 시간이 두 배로 들고, 내 파일이 남의 서버 디스크를 한 번 밟고 지나간다. 무엇보다 인터넷이 죽은 곳에서는 아예 손을 쓸 수가 없다. 공식 사이트 localsend.org가 “Share files without the cloud. Fast, private, offline.”을 슬로건으로 내건 배경이 이것이다. 광고도 추적도 로그인도 없다. 참고로 저장소에는 한국어 README도 따로 관리되고 있어서, 영어가 부담스러운 주변 사람에게 권할 때 쓸 만하다.
LocalSend 사용법의 시작, 설치는 한 줄이면 끝난다

LocalSend 사용법의 첫 단계인 설치는 명령어 한 줄이면 끝난다. macOS는 brew install --cask localsend, Windows는 winget install LocalSend.LocalSend, Linux는 flatpak install flathub org.localsend.localsend_app이다. 그 외에도 Windows는 Scoop, Chocolatey, EXE 인스톨러, 포터블 ZIP을, Linux는 Nixpkgs, Snap, AUR, AppImage, TAR, DEB를 지원하며, 모바일은 Play Store, F-Droid, APK, App Store 경로가 있다.
# Windows (Winget)
winget install LocalSend.LocalSend
# macOS (Homebrew), formula가 아니라 cask다
brew install --cask localsend
# Linux (Flathub)
flatpak install flathub org.localsend.localsend_app
# Arch Linux (AUR)
yay -S localsend-binShellScript네 명령 모두 GUI 앱 본체를 설치한다. macOS 쪽은 CLI 도구가 아니라 애플리케이션 번들이라 formula가 아닌 cask로 등록되어 있다. 동명의 formula가 없어서 --cask를 빼도 Homebrew가 알아서 cask를 찾아주긴 하지만, 명시해 두는 편이 의도가 분명하다. 패키지 ID가 기억나지 않는다면 공식 다운로드 페이지에서 플랫폼별 경로를 확인하는 쪽이 빠르다.
패키지 매니저를 권하는 데는 편의성 말고 다른 사정이 있다. README가 대놓고 밝히듯 이 앱에는 자동 업데이트가 없다. EXE 인스톨러나 DMG로 직접 깔면 새 버전이 나와도 알려줄 주체가 없어서, 몇 달 전 버전을 그대로 쓰고 있게 되기 십상이다. Winget이나 Homebrew로 깔아 두면 적어도 winget upgrade, brew upgrade 사이클에 딸려 올라간다. 뒤에서 다루겠지만 v1.17.0에 보안 패치가 들어 있어서, 이 차이가 생각보다 크게 걸린다.
Linux는 데스크톱 환경에 따라 의존성이 하나 붙는다. GNOME은 xdg-desktop-portal과 xdg-desktop-portal-gtk, KDE는 xdg-desktop-portal과 xdg-desktop-portal-kde가 필요하다. 파일 선택 다이얼로그가 안 뜬다면 대개 이쪽이다.
양쪽 기기에 모두 설치해야 하는가

원칙적으로는 그렇다. 안드로이드와 맥북이 서로를 목록에 띄우려면 양쪽 다 멀티캐스트 그룹에 참여해 announcement를 주고받아야 하는데, 그 일을 하는 게 앱이기 때문이다. 다만 받는 쪽에는 예외가 있다. 앱의 Share via link 기능을 쓰면 보내는 기기가 스스로 HTTP 서버가 되고, 받는 쪽은 브라우저로 그 주소를 열어 내려받기만 하면 된다.
정리하면 보내는 쪽에는 앱이 반드시 있어야 하고, 받는 쪽은 브라우저로 대체할 수 있다. 관리자 권한이 없는 회사 PC나 잠깐 빌린 남의 컴퓨터로 파일을 받아야 하는 상황이 정확히 이 기능의 자리다. 인터넷도 필요 없고 상대 컴퓨터에 아무것도 깔지 않는다. Windows에서 설치 자체가 막혀 있다면 포터블 ZIP도 우회로가 된다. 실제 사용 절차는 바로 다음 장의 링크로 공유하기에서, 그 밑에 깔린 프로토콜은 뒤쪽 REST 섹션에서 다룬다.
첫 파일이 건너가기까지 60초
기본적인 LocalSend 사용법은 세 동작으로 끝난다. 양쪽 기기에서 앱을 실행하고, 보내는 쪽에서 파일을 고른 뒤 화면에 뜬 상대 기기 이름을 누르고, 받는 쪽에서 수락을 누르면 된다. 두 기기가 같은 Wi-Fi에 있어야 한다는 조건 하나만 지키면 페어링도, QR 코드도, 계정 연결도 필요 없다.
LocalSend 사용법에서 의외로 먼저 손대게 되는 게 기기 이름이다. 이 이름은 설치 직후 자동으로 배정된다. 프로토콜 스펙의 예시에 등장하는 “Nice Orange” 같은 형용사와 명사의 조합인데, 이 이름이 곧 화면에 뜨는 별명(alias)이다. 근처에 같은 앱을 켠 사람이 여럿이면 오발송이 생길 수 있으므로 별명을 알아보기 쉬운 값으로 바꿔 두면 사고가 줄어든다.
갤럭시에서 맥북으로, 화면에 뜨는 순서 그대로

가장 흔한 조합인 안드로이드에서 맥북으로 사진을 넘기는 경우로 LocalSend 사용법을 실제 화면 순서대로 따라가 본다. 앱은 크게 보내기, 받기, 설정 세 탭으로 되어 있고, 실제로 오가는 일은 앞의 두 탭에서 끝난다.
받는 쪽인 맥북에서는 받기 탭을 열어 두기만 하면 된다. 이 화면에는 IP, 포트, 별명이 표시되는데, 포트는 앞서 나온 53317이고 별명은 갤럭시 화면에 뜰 이름이라 미리 봐 두면 헷갈리지 않는다. 따로 대기 버튼을 누를 필요는 없다.
보내는 쪽인 갤럭시에서는 보내기 탭에서 무엇을 보낼지 고른다. 선택지는 파일, 폴더, 미디어, 텍스트, 앱, 붙여넣기 여섯 가지다. 사진이라면 미디어를 고르고, v1.17.0부터는 스와이프로 드래그하듯 여러 장을 한 번에 집을 수 있다. 고르고 나면 아래 근처 기기 목록에 맥북의 별명이 뜨고, 그걸 누르면 전송 요청이 날아간다. 목록이 비어 있으면 기기 검색을 다시 돌리거나 수동 전송으로 IP를 직접 넣을 수도 있다.
이때부터는 두 화면이 짝을 이룬다. 갤럭시에는 “응답 대기 중…”이 뜨고, 맥북에는 “(갤럭시 별명)에서 3개의 파일을 보내려고 합니다” 같은 문구와 함께 수락과 거부 버튼이 나온다. 맥북에서 수락을 누르는 순간 전송이 시작되고, 파일마다 완료 또는 대기 중 상태가 붙으면서 전체 진행률과 경과 시간이 아래에 표시된다. 여기까지가 전부다. 상대가 거부하면 보내는 쪽에 “받는 사람이 요청을 거부했습니다”가 뜬다.
안드로이드에서는 갤러리나 파일 앱에서 사진을 고른 뒤 시스템 공유 버튼으로 LocalSend를 선택하는 방법이 더 빠르다. 앱 안내에도 “휴대전화의 ‘공유’ 기능을 사용하면 보다 편리하게 파일을 선택할 수 있습니다”라고 적혀 있다.
받은 파일은 어디로 가는가

기본은 다운로드 폴더다. LocalSend 사용법에서 처음 받은 파일을 못 찾아 헤매는 일이 잦은데, 설정 탭의 받기 항목에 저장 위치가 있고, 기본값이 다운로드 폴더로 잡혀 있어 따로 건드리지 않아도 된다. 원하면 LocalSend 전용 폴더나 임의 경로로 바꿀 수 있다. 사진과 동영상을 앨범에서 바로 보고 싶다면 갤러리에 미디어 저장을 켜면 되는데, 저장 위치를 특정 폴더로 지정해 두면 이 옵션은 자동으로 꺼진다.
같은 화면에 빠른 저장이 있다. 이걸 켜면 수락 버튼을 누르는 과정 없이 바로 받는다. 다만 아무나 보낸 파일이 자동으로 떨어지는 셈이라, 뒤에 나오는 즐겨찾기 한정 옵션과 함께 쓰는 편이 안전하다. 전송 기록 보관을 켜 두면 나중에 무엇을 언제 받았는지 확인할 수 있다.
파일만 보내는 게 아니다

의외로 덜 알려진 LocalSend 사용법이 텍스트 전송이다. 보내기 탭의 선택지에 텍스트와 붙여넣기가 있는 게 실용적이다. 긴 URL이나 와이파이 비밀번호, 인증번호처럼 타이핑하기 애매한 문자열을 그대로 보낼 수 있다. 받는 쪽에는 “(별명)에서 메시지를 보냈습니다:”라고 뜨면서 내용이 표시되고, 링크를 보내면 눌러서 바로 열 수 있는 형태로 온다. 폰에서 복사한 주소를 PC 브라우저로 넘길 때 카카오톡을 켜지 않아도 되는 셈이다.
여러 기기에 같은 파일을 한꺼번에 뿌리는 것도 된다. 전송 모드를 하나의 기기에 보내기 대신 여러 기기에 보내기로 바꾸면 된다. 회의실에서 같은 자료를 여러 사람에게 돌릴 때 쓰는 방식이다.
앱이 없는 상대에게 보내기, 링크로 공유하기

상대 컴퓨터에 LocalSend를 깔 수 없는 상황에서 알아 두면 가장 요긴한 LocalSend 사용법이다. 전송 모드에서 링크로 공유하기를 고른다. 보낼 파일을 정하고 이 모드를 켜면 앱이 “서버 시작 중…”을 거쳐 “해당 링크를 브라우저에서 여세요:”라며 주소를 하나 띄운다. 상대는 그 주소를 브라우저에 입력하기만 하면 파일 이름과 크기가 담긴 목록을 보고 내려받을 수 있다. 상대 쪽에 설치할 것도, 계정도, 인터넷도 필요 없다.
이 화면에는 안전장치가 두 개 붙어 있다. PIN 번호 사용을 켜면 앱이 PIN을 하나 만들어 보여 주고, 브라우저 쪽에서는 “PIN 번호 입력” 화면이 먼저 뜬다. 카페 같은 공용 Wi-Fi에서 링크 주소만 아는 사람이 끼어드는 걸 막는 용도다. 요청 자동 수락을 켜면 상대가 접속할 때마다 수락을 누르지 않아도 된다. 앱 화면에는 “아직 요청이 없습니다.”가 떠 있다가 상대가 들어오면 요청 목록에 잡히므로, 누가 받아 갔는지 눈으로 확인된다.
암호화 옵션을 켠다면 감안할 게 하나 있다. 앱이 직접 “LocalSend는 자체 서명된 인증서를 사용합니다. 브라우저에서 이를 허용해야 합니다”라고 안내하는데, 상대 브라우저에 경고 화면이 뜨고 예외 처리를 해 줘야 한다는 뜻이다. 상대가 비개발자라면 이 경고에서 겁먹고 멈추는 경우가 많아, 링크 공유만큼은 기본값인 평문 HTTP로 두고 대신 PIN을 거는 쪽이 실전에서 매끄럽다.
즐겨찾기 기기만 자동 수신하게 만들려면
자주 쓰는 조합, 예를 들어 자기 폰과 자기 데스크톱은 즐겨찾기로 등록해 두는 편이 낫다. v1.16.0에서 즐겨찾기 기기에 한해서만 자동 수신을 켜는 옵션이 들어왔기 때문에, 신뢰하는 기기에서 오는 파일만 수락 버튼 없이 바로 받게 만들 수 있다. 모든 발신자에게 자동 수신을 열어 두는 것과 달리 위험 표면이 훨씬 좁다.
모바일에서 사진 여러 장을 고를 때는 v1.17.0에서 추가된 스와이프 제스처로 드래그하듯 다중 선택이 된다. Windows에서 스크린샷을 클립보드에 복사한 뒤 붙여넣으면 자동으로 PNG로 변환되어 전송 목록에 올라가는 것도 같은 릴리스에서 들어온 편의 기능이다.
트레이 상주와 포터블 모드
데스크톱에 띄워 두고 쓸 생각이라면 LocalSend 사용법에서 실행 옵션 두 개를 더 챙길 만하다.
# 트레이에만 띄우고 창 없이 시작 (v1.15.0 기준)
localsend_app.exe --hiddenShellScript--hidden 플래그는 앱을 트레이에만 올리고 창을 띄우지 않는다. 부팅 시 자동 실행에 걸어 두고 상시 수신 대기 상태로 쓸 때 쓰는 옵션이다. v1.14.0 이하에서는 autostart 플래그가 설정되고 hidden 설정이 켜져 있을 때만 숨김 시작이 되는 방식이었으므로, 예전 버전 기준으로 찾아본 설명과 다르게 동작한다면 버전을 먼저 확인해야 한다.
여기에 더해 v1.13.0부터는 포터블 모드가 있다. 실행 파일과 같은 디렉토리에 settings.json이라는 파일을 만들어 두면, 파일 내용이 비어 있어도 앱이 기본 설정 경로 대신 그 파일에 설정을 저장한다. USB에 넣어 들고 다니거나 시스템에 흔적을 남기고 싶지 않을 때 쓰는 방식이다.
224.0.0.167:53317, 기기들은 서로를 어떻게 찾는가

여기서부터는 개발자용 심화다. 앞의 LocalSend 사용법이 “무엇을 누르는가”였다면, 이제는 그 버튼 뒤에서 무슨 패킷이 오가는지를 본다. LocalSend의 디스커버리는 UDP 멀티캐스트를 기본으로 한다. 앱이 시작되면 멀티캐스트 주소 224.0.0.167, 포트 53317로 자신을 알리는 announcement를 쏘고, 이를 받은 기기는 HTTP/TCP로 POST /api/localsend/v2/register를 호출해 응답하거나 멀티캐스트 UDP로 폴백한다. 즉 상대를 찾는 순간부터 이미 표준 프로토콜만 쓴다.
{
"alias": "Nice Orange",
"version": "2.0",
"deviceModel": "Samsung",
"deviceType": "mobile",
"fingerprint": "random string",
"port": 53317,
"protocol": "https",
"download": true,
"announce": true
}JSON이 JSON이 멀티캐스트 그룹에 뿌려지는 announcement의 실제 본문이다. port와 protocol은 이후 HTTP 세션을 어디로 열어야 하는지를 알려주고, fingerprint는 기기를 구분하는 식별자 역할을 한다. 눈여겨볼 건 announce 플래그다. 스펙은 이 값이 true일 때만 응답이 발생한다고 규정하고, 그 응답 메시지에는 announce: false를 담게 해 두었다. 인사를 받은 쪽이 다시 인사를 유발해 무한히 되튀는 상황을 이 한 칸으로 막는 것이다. deviceType은 mobile, desktop, web, headless, server 중 하나이며 headless는 GUI 없는 터미널 프로그램, server는 24/7 클라우드 서비스를 가리킨다. 이 값으로 동작이 갈릴 것 같지만 스펙은 아이콘 표시 같은 UI 용도일 뿐 프로토콜상 차이는 없다고 못 박는다. 그럼에도 headless와 server를 열거해 둔 것 자체는 GUI 없는 구현체를 처음부터 상정했다는 신호로 읽힌다.
멀티캐스트가 막힌 네트워크를 위해 HTTP 레거시 모드가 별도로 준비되어 있다. 이 모드는 로컬 서브넷의 모든 IP에 HTTP 요청을 직접 보내 응답하는 기기를 찾는 방식, 즉 스캔이다. 느리지만 멀티캐스트 패킷을 드롭하는 공유기에서도 동작한다.
fingerprint는 이름 때문에 오해하기 쉬운 필드다. protocol이 https일 때는 인증서의 SHA-256 해시가, http일 때는 무작위 문자열이 들어간다. 그런데 스펙은 이 값의 용도를 자기 자신을 발견 대상에서 빼고(self-discovery 방지) 기기를 기억하는 것으로 못 박아 두었다. HTTPS 모드에서는 요청 본문의 fingerprint를 아예 무시한다고 명시되어 있다. 즉 상대를 인증하는 수단이 아니다.

README에 따르면 TLS/SSL 인증서는 각 기기에서 즉석 생성된다. 중앙 CA가 없으니 인증서 체인 검증도 성립하지 않는다. 결국 이 구조에서 신뢰의 근거는 암호학이 아니라 “같은 로컬 네트워크에 있다”는 전제 자체다. 전송 구간은 암호화되지만, 공용 Wi-Fi에서 아무나 보낼 수 있다는 문제는 별개이고 그건 뒤에 나올 PIN이 담당한다.
prepare-upload에서 upload까지, REST로 뜯어본 LocalSend 사용법

LocalSend 사용법을 API 레벨에서 보면 전송은 명확한 세션 모델이다. 흐름은 prepare-upload로 세션을 열어 sessionId와 파일별 token을 받고, 그 토큰으로 upload를 호출해 바이너리를 밀어 넣고, 필요하면 cancel로 끊는 순서다. 파일 여러 개는 병렬 업로드가 가능하다.
{
"info": {
"alias": "Nice Orange",
"version": "2.0",
"deviceModel": "Samsung",
"deviceType": "mobile",
"fingerprint": "random string",
"port": 53317,
"protocol": "https",
"download": true
},
"files": {
"file_id_1": {
"id": "file_id_1",
"fileName": "image.png",
"size": 324242,
"fileType": "image/jpeg",
"sha256": "hash_value",
"preview": "preview_data",
"metadata": {
"modified": "2021-01-01T12:34:56Z",
"accessed": "2021-01-01T12:34:56Z"
}
}
}
}JSONPOST /api/localsend/v2/prepare-upload?pin=123456의 요청 본문이다. info는 발신 기기 자신을 소개하는 블록이고 files는 보내려는 파일의 메타데이터 맵이다. 수신 측이 수락 UI를 그리려면 파일 이름과 크기를 미리 알아야 하므로 실제 바이트보다 메타데이터가 먼저 간다. sha256은 무결성 검증을 위해 정의된 필드이나 nullable이라 항상 채워지지는 않고, metadata는 수정 시각 같은 정보를 보존하는 데 쓰인다.
{
"sessionId": "mySessionId",
"files": { "file_id_1": "file_token_1" }
}JSON수신 측이 수락하면 이 응답이 돌아온다. sessionId 하나에 파일 개수만큼의 token이 매핑되는 구조이며, 이 token 없이는 업로드가 거부된다. 응답 코드는 상황을 꽤 촘촘하게 구분해서, 204는 전송할 것이 없어 이미 완료된 상태, 400은 잘못된 본문, 401은 PIN이 필요하거나 무효, 403은 사용자가 거부, 409는 다른 세션이 이미 점유 중, 429는 요청 과다, 500은 알 수 없는 오류다.
upload 엔드포인트의 3중 파라미터 검증
세션이 열렸으면 이제 바이트를 밀어 넣는다. upload는 sessionId, fileId, token 세 개를 쿼리 파라미터로 요구하고, 셋이 모두 맞아야 통과한다.
POST /api/localsend/v2/upload?sessionId=mySessionId&fileId=file_id_1&token=file_token_1
Content-Type: application/octet-stream
<binary body>Plaintext실제 바이트가 흐르는 지점이다. 바디는 순수 바이너리이고 응답 바디는 없다. 파라미터가 빠지면 400, 토큰이나 IP가 유효하지 않으면 403, 다른 세션에 막히면 409가 돌아온다. 세 파라미터가 모두 맞아야 통과하므로 sessionId만 알아낸 제3자가 임의 파일을 밀어 넣기는 어렵다. 세션을 중단하려면 POST /api/localsend/v2/cancel?sessionId=mySessionId를 호출한다.
브라우저로 받을 때 쓰는 prepare-download
받는 쪽이 거꾸로 끌어오는 흐름도 있다. 스펙이 Reverse File Transfer, 별칭 Download API라고 부르는 것으로 POST /api/localsend/v2/prepare-download로 파일 목록을 받고 GET /api/localsend/v2/download?sessionId=mySessionId&fileId=file_id_1로 바이너리를 내려받는다. PIN이 걸려 있으면 앞쪽에 ?pin=123456을 붙인다. ?sessionId=도 붙일 수 있는데 필수는 아니고, 사용자가 브라우저를 새로고침했을 때 같은 세션임을 알려 이어받게 하는 용도다.
다만 이건 대칭적인 대안이 아니라 용도가 정해진 폴백이다. 스펙은 수신 측에 LocalSend가 없을 때 쓰라고 못 박아 두었고, 이게 앱에서 Share via link라고 부르는 그 기능이다. 앞서 “받는 쪽은 브라우저로 대체할 수 있다”고 한 근거가 여기에 있다.
주소를 눈여겨볼 만하다. 받는 사람이 브라우저에 여는 곳은 어떤 중계 사이트가 아니라 http://<보내는기기IP>:<포트>, 즉 보내는 기기 자신이다. 발신 기기가 그 순간 웹 서버가 되는 구조라 여전히 아무 서버도 경유하지 않는다. 이 경로만 평문 HTTP인 것도 이유가 있는데, 브라우저가 자체 서명 인증서를 거부하기 때문이라고 스펙에 적혀 있다. 앱 설정에서 링크 공유에 PIN을 걸거나 HTTPS를 켜는 옵션도 있다.
한 가지 헷갈리기 쉬운 게 있다. 이름이 비슷한 web.localsend.org는 이 기능과 별개다. 그쪽은 WebRTC와 WebSocket 기반이고 저장소 README가 밝히듯 시그널링 서버를 필요로 하는데, 기본값이 공개 서버(wss://public.localsend.org/v1/ws)라 인터넷이 있어야 한다. 지금까지 설명한 멀티캐스트와 REST 경로와는 다른 물건이며, 릴리스가 멈춘 원인인 WebRTC 작업과 같은 줄기다. 오프라인에서 앱 없는 기기로 받는 게 목적이라면 Share via link 쪽이 맞다. 전체 스펙은 localsend/protocol 저장소에 v2.1 기준으로 공개되어 있어서 직접 클라이언트를 짜는 것도 가능하다.
안 보인다면 십중팔구 방화벽이다

LocalSend에서 상대 기기가 목록에 안 뜨는 원인은 세 가지로 좁혀진다. 53317 포트 차단, 공유기의 멀티캐스트 패킷 드롭, 게스트 Wi-Fi의 AP isolation이다. 해결책은 수신 방향으로 TCP와 UDP 53317을 둘 다 열고, 공유기의 AP isolation을 끄는 것이다. 공식 문서가 요구하는 방화벽 조건은 아래 표가 전부다.
| 트래픽 방향 | 프로토콜 | 포트 | 정책 |
|---|---|---|---|
| Incoming | TCP, UDP | 53317 | Allow |
| Outgoing | TCP, UDP | Any | Allow |
수신은 53317 하나만 열면 되지만 TCP와 UDP를 모두 열어야 한다는 점이 함정이다. 디스커버리는 UDP를 쓰고 실제 전송은 TCP를 쓰기 때문에, 하나만 열면 기기는 보이는데 전송이 안 되거나 그 반대의 어중간한 증상이 나온다. 여기에 더해 공유기의 AP isolation을 꺼야 하는데, 기본값은 보통 비활성이지만 게스트 네트워크에서는 켜져 있는 경우가 많다.
기기가 살아 있는지 curl로 확인하는 법
방화벽을 열었는데도 안 잡힌다면 앱 화면을 믿지 말고 직접 찔러 보는 편이 빠르다. 포트를 열고 상대가 응답하는지 확인하는 데까지 명령 몇 줄이면 된다.
# Linux: UDP/TCP 53317 열기 (ufw 사용 시)
sudo ufw allow 53317/udp
sudo ufw allow 53317/tcp
# 상대 기기가 살아 있는지 직접 확인 (암호화 기본값 기준)
curl -k https://192.168.0.15:53317/api/localsend/v2/info
# 설정에서 암호화를 꺼 둔 기기라면 이쪽
curl http://192.168.0.15:53317/api/localsend/v2/infoShellScript앞의 두 줄은 공식 방화벽 요구사항을 ufw로 그대로 옮긴 것이다. 둘 다 53317이라 하나만 열고 끝냈다고 착각하기 쉬우니 프로토콜을 반드시 구분해야 한다. /api/localsend/v2/info는 원래 디스커버리에 쓰이다가 양방향 방식인 /register로 대체된 구식 경로다. 스펙이 디버깅 용도로만 쓰라고 남겨 둔 것인데, 지금 우리가 하려는 일이 정확히 그 디버깅이다. 응답이 오면 최소한 HTTP 계층은 살아 있다는 뜻이다.
LocalSend 사용법을 다루는 글에서 자주 빠지는 대목이 여기다. -k와 https가 핵심이다. LocalSend는 암호화가 기본값으로 켜져 있어서 평문 http://로 찌르면 응답 없이 끊긴다. 이걸 모르고 http://로 확인했다가 “역시 방화벽이구나” 하고 엉뚱한 데를 파는 경우가 실제로 많다. TLS 인증서를 각 기기가 즉석에서 만들어 쓰는 자체 서명 방식이라 -k로 검증을 건너뛰어야 하는 것도 같은 이유다.
{
"alias": "Nice Orange",
"version": "2.0",
"deviceModel": "Samsung",
"deviceType": "mobile",
"fingerprint": "random string",
"download": true
}JSONGET /api/localsend/v2/info가 돌려주는 응답이다. 이 JSON이 보이는데도 앱 화면에는 기기가 안 뜬다면 TCP는 통하고 UDP 멀티캐스트만 막혔다는 뜻이므로 범인이 좁혀진다. 이때는 HTTP 레거시 모드를 켜서 스캔으로 우회하거나, 상대 IP를 수동으로 추가하는 쪽이 현실적이다. 아무 응답도 없다면 방화벽이나 AP isolation 쪽이다.
VPN·Docker 가상 어댑터가 범인일 때
개발자 노트북이 특히 잘 걸리는 함정이 하나 더 있다. VPN 클라이언트에 Docker, WSL 가상 어댑터까지 여러 개 붙어 있으면 앱이 엉뚱한 인터페이스로 announcement를 쏜다. v1.17.0에 들어온 네트워크 인터페이스 필터링 설정이 딱 이 경우를 위한 것이라, 쓸 인터페이스를 직접 지정하면 끝난다. Windows에서 회사 정책 때문에 방화벽 예외를 못 만드는 상황이라면, 네트워크 프로필이 공용으로 잡혀 있진 않은지부터 확인하는 순서가 빠르다.
에어드랍, 스냅드롭, 클라우드와 뭐가 다른가

LocalSend 사용법을 익힐 가치가 있는지는 쓰는 환경이 결정한다. 애플 기기끼리만 쓴다면 AirDrop이 여전히 가장 매끄럽다. 설치가 불가능한 남의 컴퓨터라면 브라우저 기반 도구가 낫다. 서로 다른 OS를 섞어 쓰면서 인터넷 없이 대용량을 자주 옮기는 경우에 LocalSend가 맞는다. 아래 표가 그 차이를 정리한 것이다.
| 항목 | LocalSend | AirDrop | Snapdrop류 웹 도구 | 클라우드 드라이브 |
|---|---|---|---|---|
| 지원 플랫폼 | Android, iOS, macOS, Windows, Linux, Fire OS | Apple 기기 전용 | 브라우저가 있는 모든 기기 | 대부분의 플랫폼 |
| 인터넷 필요 | 불필요 | 불필요 | 시그널링 서버 접속 필요 | 필수 |
| 계정/로그인 | 불필요 | 불필요 | 불필요 | 필요 |
| 전송 경로 | 로컬 네트워크 직결 | 기기 간 직결 | P2P (시그널링은 서버 경유) | 외부 서버 왕복 |
| 앱 설치 | 보내는 쪽만 필수 (받는 쪽은 브라우저 가능) | 불필요 (기본 탑재) | 불필요 | 보통 필요 |
| 라이선스 | Apache-2.0 오픈소스 | 비공개 | 도구마다 다름 | 비공개 |
| 속도 결정 요인 | 로컬 네트워크 대역폭 | 기기 간 무선 대역폭 | 로컬 또는 인터넷 | 업로드+다운로드 회선 |
표에서 실무적으로 가장 큰 갈림길은 “인터넷 필요” 행이다. 클라우드는 4GB 파일을 옮길 때 업로드와 다운로드를 각각 한 번씩, 즉 총 8GB의 인터넷 트래픽을 발생시키는 반면 LocalSend는 공유기 안에서 4GB만 흐른다. 회의실이나 촬영 현장처럼 회선이 좋지 않은 곳에서 이 차이는 몇 분과 몇십 분의 차이가 된다.
LocalSend 릴리스가 17개월째 멈춰 있는 이유

LocalSend의 최신 안정 릴리스는 2025년 2월 20일자 v1.17.0이고, 2026년 7월 기준 약 17개월 전이다. 다만 이건 방치가 아니라 WebRTC 지원 작업이 진행 중이기 때문이며, GitHub main 브랜치에는 거의 매일 커밋이 쌓이고 있다. v1.17.0 릴리스 노트 자체가 스스로를 “WebRTC가 안정화되기 전의 작은 중간 릴리스”라고 소개한다.
v1.0.0이 2022년 12월 29일에 나온 이후 v1.15.3(2024-07-29), v1.15.4(2024-08-20), v1.16.x대(2024-11)처럼 몇 달 간격으로 이어지던 리듬이 v1.17.0 이후로 눈에 띄게 길어진 건 사실이다. 그래서 검색하다 보면 죽은 프로젝트 아니냐는 이야기도 나온다.
정작 GitHub 저장소를 열어 보면 딴판이다. main 브랜치에는 Windows 공유 시트, CLI의 --text / -t 플래그를 통한 텍스트 공유, Android 빠른 설정 타일, 전송 후 wake lock 해제, 미디어 공유 시 위치 메타데이터 보존, Linux CJK 폰트 지원 같은 변경이 계속 들어가고 있다. 마지막 항목인 한중일 텍스트 렌더링 개선은 한국어를 쓰는 입장에서 특히 반가운 축이다. 릴리스 번호만 안 붙었을 뿐 코드는 돌고 있다.
지금 LocalSend 사용법을 익히는 입장에서 실무적으로 남는 결론은 이렇다. 안정 채널만 쓴다면 지금 손에 들어오는 물건은 v1.17.0에서 멈춰 있고, 위에 나열한 개선들은 아직 못 받는다. 대신 v1.17.0에는 경로 순회(path traversal) 취약점 보안 패치가 들어 있다. 그 이전 버전을 쓰고 있다면 업데이트는 선택 사항이 아니다. 파일을 받아서 디스크에 쓰는 앱에서 경로 순회는 수신 파일이 의도한 저장 폴더 바깥에 쓰이게 만들 수 있는 종류의 문제다.
앞서 패키지 매니저로 깔라고 한 게 여기서 다시 걸린다. 자동 업데이트가 없는 앱이다 보니, 직접 설치한 사용자는 자기가 1년 넘은 취약 버전을 붙들고 있다는 사실조차 모르기 쉽다.
FAQ
LocalSend는 완전 무료인가?
완전 무료다. Apache-2.0 라이선스로 공개된 오픈소스이며 유료 플랜도, 광고도, 계정 등록도 없다. v1.13.0부터 앱에 후원(donation) 항목이 들어가 있긴 하지만 기능을 잠그는 장치가 아니라 순수한 선택이다. 애초에 파일이 서드파티 서버를 거치지 않으니 사업자가 과금할 트래픽 자체가 발생하지 않는 구조다.
LocalSend는 인터넷 없이도 정말 동작하는가?
동작한다. 두 기기가 같은 로컬 네트워크에 붙어 있기만 하면 되고, 그 네트워크가 외부 인터넷에 연결되어 있을 필요는 없다. 디스커버리는 224.0.0.167:53317 멀티캐스트로, 전송은 같은 포트의 HTTP/TCP로 이루어지므로 모든 통신이 공유기 내부에서 끝난다. 인터넷이 끊긴 사무실 Wi-Fi나 스마트폰 핫스팟만으로도 전송이 된다.
LocalSend로 보낸 파일이 서버에 남는가?
남지 않는다. 서드파티 서버를 아예 경유하지 않고 발신 기기에서 수신 기기로 직접 HTTP 요청이 가는 구조이기 때문이다. 계정과 로그인이 없고 광고나 추적도 없으며, TLS 인증서는 각 기기에서 즉석 생성된다. 소스는 Apache-2.0 라이선스로 공개되어 있어 이 주장을 코드로 확인할 수 있다. 파일은 수신 기기의 지정된 저장 폴더에만 떨어진다.
상대 기기가 목록에 안 뜨는데 어떻게 확인하는가?
같은 Wi-Fi 여부 확인, 수신 방향 TCP·UDP 53317 개방, curl 응답 확인 순서로 좁힌다. curl -k https://상대IP:53317/api/localsend/v2/info가 JSON을 돌려주면 HTTP 계층은 통하고 멀티캐스트만 막힌 것이므로, 인터페이스 필터링을 조정하거나 HTTP 레거시 모드를 쓰면 된다. 아무 응답이 없다면 방화벽이나 공유기의 AP isolation 쪽이다. 암호화가 기본값이라 http://로 확인하면 멀쩡한 기기도 응답하지 않으니 주의한다.
PIN을 걸어서 아무나 못 보내게 할 수 있는가?
가능하다. 프로토콜 스펙에 PIN 인증이 정의되어 있어 prepare-upload와 prepare-download 호출 시 ?pin=123456 형태의 쿼리 파라미터로 전달되며, PIN이 없거나 틀리면 401이 반환된다. 여기에 더해 즐겨찾기로 등록한 기기에서 오는 전송만 자동 수신하도록 제한하는 옵션(v1.16.0)을 함께 쓰면 실수로 수락 버튼을 누르는 사고도 줄어든다.
마치며

LocalSend 사용법을 익힌 계기는 거창하지 않았다. 장애 대응 중에 안드로이드 폰으로 찍어 둔 서버랙 사진 수십 장을 맥북으로 옮겨야 했는데, 사내망은 외부 클라우드가 막혀 있었고 케이블은 손에 없었다. 결국 슬랙 나에게 보내기로 몇 장씩 나눠 올리다가 짜증이 나서 검색한 것이 시작이었다. 설치하고 실제로 파일이 넘어가기까지 1분이 안 걸렸고, 그 뒤로 노트북을 새로 세팅할 때마다 습관처럼 깔게 됐다.
계속 쓰게 된 건 오히려 나중 일이다. 어느 날 프로토콜 스펙을 열어 보고 curl로 /api/localsend/v2/info를 직접 때려 봤는데, 이 앱이 하는 일이 결국 멀티캐스트 announcement 하나와 REST 엔드포인트 몇 개가 전부라는 게 눈에 들어왔다. 그때부터 좀 다르게 보였다. 안 되면 내가 뜯어볼 수 있는 물건과, 안 되면 그냥 안 되는 물건은 다르다. 실제로 회사 노트북에서 기기가 안 잡혔을 때 방화벽 두 줄로 끝냈던 것도 스펙을 한 번 읽어 둔 덕이었다.
v1.17.0이 17개월째 최신인 건 솔직히 아쉽다. 커밋은 매일 도는데 릴리스만 안 나오는 프로젝트를 볼 때의 그 애매한 기분이 있다. WebRTC 작업이 크다니 사정을 모르는 바는 아니지만, main에 들어간 CJK 폰트 개선 같은 건 한국어 쓰는 입장에서 빨리 받고 싶은 게 사실이다. 그래도 갤럭시에서 맥북으로 영상 하나 넘기는 일에 v1.17.0이 모자란 적은 아직 없었다.
