はじめに
Notionを使っていましたが、Obsidian(オブシディアン:「黒曜石」の意味)に乗り換えました。Obsidianと自分のWebDAVサーバを組み合わせれば、iPhoneと自宅PCなどのマークダウンメモをクラウドサービスに依存せずに高度に連携できると思ったからです。
クラウドサービスが紛争で影響を受けたり、いくら暗号化されているとはいえ、業務のメモやプライベートな情報は、自分が管理するストレージに置きたい、と思ったのがきっかけでした。
Githubのソースコード修正までして、ようやく実現!(誰かほめてw)
なぜObsidianなのか
Obsidianは、単なるメモアプリではなく、Markdownベースの"自分専用知識ベース"を育てられる環境です。しかもローカルLLMやRAGとの相性がよく、生成AIで得た知見を一過性の会話で終わらせず、再利用可能な資産に変えやすいです。この点が、生成AI界隈で注目される大きな理由でもあります。
ファイルはただのMarkdownなので、Gitでバージョン管理もできますし、どんなエディタでも開けます。特定のクラウドサービスにロックインされないのが最大の利点です。
本記事では、自宅サーバにWebDAVを立てて、Windows版ObsidianとiPhone版Obsidianの双方向同期を実現した手順を紹介します。途中、iOS版でSSL接続が一切通らないという深刻な問題にぶつかり、Remotely Saveプラグインのソースコードを修正して解決しました。その全過程を記録します。
最終構成
最終的に動作した構成は以下の通りです。
iPhone Obsidian
↕ Remotely Save(修正版: fetch() for iOS)
↕ HTTPS
Nginx WebDAVサーバ(Ubuntu 24.04)
↕ HTTPS
↕ Remotely Save(オリジナル版: requestUrl())
Windows 11 Obsidian
同期は双方向です。iPhoneで書いたノートはWindowsに、Windowsで書いたノートはiPhoneに反映されます。WebDAVサーバは自宅LAN内に閉じており、外出先からはVPN経由でアクセスします。
Step 1: サーバ環境構築
Nginx + WebDAVモジュールのインストール
Obsidianの Remotely Save プラグインはWebDAVのLOCK/UNLOCKメソッドを使うため、通常のnginxパッケージでは動きません。nginx-extras をインストールすると、LOCK/UNLOCK対応の dav_ext モジュールが含まれます。
sudo apt update
sudo apt install nginx nginx-extras
Vault用ディレクトリの作成
Obsidianの保管庫(Vault)を格納するディレクトリを作成します。Nginx(www-dataユーザー)が書き込めるよう、権限を設定しておきます。
mkdir -p /home/<USER>/obsidian-vault
sudo chown <USER>:www-data /home/<USER>/obsidian-vault
sudo chmod 775 /home/<USER>/obsidian-vault
chmod o+x /home/<USER>
最後の chmod o+x は、www-dataユーザーがホームディレクトリを通過してVaultディレクトリにアクセスするために必要です。これを忘れると404エラーになります。
Basic認証の設定
WebDAVへの不正アクセスを防ぐため、Basic認証を設定します。
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd <USER>
パスワードを聞かれるので、設定してください。
ここまでで、Nginx + WebDAVの基盤が整いました。次はSSL証明書を準備します。
Step 2: SSL証明書の作成(mkcert)
自宅LAN内のプライベートIPに対してLet's Encryptは使えません。そこで、mkcertを使ってローカルCA(認証局)と証明書を生成します。
Windows11でCA作成と証明書生成
mkcertのインストールとCA作成は、普段使っているWindows PCで行います。
winget install FiloSottile.mkcert
インストール後、PowerShellを再起動してください。その後、ローカルCAを作成し、サーバ用の証明書と秘密鍵を生成します。
mkcert -install
mkcert <SERVER_IP> <HOSTNAME>
<SERVER_IP>+1.pem(証明書)と <SERVER_IP>+1-key.pem(秘密鍵)が生成されます。これをサーバに転送します。
scp <SERVER_IP>+1.pem <USER>@<SERVER_IP>:~/
scp <SERVER_IP>+1-key.pem <USER>@<SERVER_IP>:~/
サーバで証明書を配置
転送した証明書と秘密鍵を、Nginxが読める場所に配置します。秘密鍵はwww-dataグループのみ読めるよう、権限を絞っておきます。
sudo mkdir -p /etc/ssl/local
sudo mv ~/<SERVER_IP>+1.pem /etc/ssl/local/cert.pem
sudo mv ~/<SERVER_IP>+1-key.pem /etc/ssl/local/key.pem
sudo chmod 644 /etc/ssl/local/cert.pem
sudo chmod 640 /etc/ssl/local/key.pem
sudo chown root:www-data /etc/ssl/local/key.pem
iPhoneにCA証明書をインストール
iPhoneでHTTPS接続を信頼させるため、mkcertのルートCA証明書をインストールします。Windows上の C:\Users\<USER>\AppData\Local\mkcert\rootCA.pem をメールで自分宛に送信し、iPhoneで以下の手順で設定します。
- メールで
rootCA.pemを開く → プロファイルがダウンロードされる - 設定 → 一般 → VPNとデバイス管理 → プロファイルをインストール
- 設定 → 一般 → 情報 → 証明書信頼設定 → mkcertのCAを有効にする
3番目のステップを忘れると、証明書がインストールされていても信頼されません。必ず有効化してください。
ここまでで、SSL証明書の準備が完了しました。次はNginxにWebDAVの設定を入れます。
Step 3: Nginx WebDAV設定
NginxにHTTPS + WebDAV + Basic認証 + CORS の設定を追加します。以下の内容で /etc/nginx/sites-available/webdav を作成してください。
server {
listen 443 ssl;
server_name <SERVER_IP>;
ssl_certificate /etc/ssl/local/cert.pem;
ssl_certificate_key /etc/ssl/local/key.pem;
access_log /var/log/nginx/access.log;
location /obsidian/ {
alias /home/<USER>/obsidian-vault/;
dav_methods PUT DELETE MKCOL COPY MOVE;
dav_ext_methods PROPFIND OPTIONS LOCK UNLOCK;
dav_access user:rw group:r all:r;
client_max_body_size 50M;
create_full_put_path on;
auth_basic "Obsidian WebDAV";
auth_basic_user_file /etc/nginx/.htpasswd;
# CORS設定(iOS対応で最も重要な部分)
set $cors_origin $http_origin;
if ($cors_origin = '') {
set $cors_origin '*';
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, OPTIONS, LOCK, UNLOCK' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Depth, Overwrite, Destination, Lock-Token, Timeout, If, Cache-Control' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
if ($request_method = OPTIONS) {
return 204;
}
}
}
設定を有効化して、Nginxを再起動します。
sudo ln -s /etc/nginx/sites-available/webdav /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
CORS設定の解説
後述するiOS修正で fetch() APIを使いますが、fetch() はCORSを強制します(Obsidianの requestUrl() はCORSをバイパスします)。iOSのObsidianはCapacitor WebView内で動作するため、Originは capacitor://localhost になります。
Access-Control-Allow-Origin: * と Access-Control-Allow-Credentials: true はHTTP仕様上、同時に使えません。そのため、リクエストのOriginヘッダーを動的に返す設定にしています。
動作確認
curlでWebDAVの動作を確認します。XMLのレスポンスが返ってくれば成功です。
curl -k -u <USER> https://<SERVER_IP>/obsidian/ -X PROPFIND
ここまでで、WebDAVサーバが動作する状態になりました。次はWindows版Obsidianから接続します。
Step 4: Windows版 Obsidian の設定
Obsidianのインストールと保管庫の作成
obsidian.md からWindows版をインストールし、起動時に保管庫(Vault)を作成します。
Remotely Saveプラグインの導入
Obsidianの設定から、コミュニティプラグイン「Remotely Save」をインストールします。
- 設定 → コミュニティプラグイン → 制限モードを解除 → 閲覧
- 「Remotely Save」を検索 → インストール → 有効化
- プラグイン設定で以下を入力します。
- リモートサービス: WebDAV
- Server Address:
https://<SERVER_IP>/obsidian/ - Username: htpasswdで設定したユーザー名
- Password: htpasswdで設定したパスワード
- Check → 接続成功を確認します
- 同期アイコン(円形矢印)をクリックして初回同期を実行します
ハマりポイント: SSL検証
mkcertの自己署名証明書を使っている場合、Windows版ObsidianのRemotely Saveは問題なく動作します。しかし、Obsidian Gitプラグインなどgitコマンドを内部で使うプラグインでは、SSL検証エラーが出る場合があります。その場合は以下で回避できます。
cd C:\Users\<USER>\obsidian-vault
git config http.sslVerify false
ここまでで、Windows PCからWebDAV経由のObsidian同期が動作しています。次はiPhoneですが、ここからが本記事の本題です。
Step 5: iOS版で発生した問題
iPhoneのObsidianにRemotely Saveをインストールし、Windowsと同じWebDAV設定を入れてCheckを押すと、以下のエラーが表示されました。
Error: Request failed. The Internet connection appears to be offline.
The webdav server cannot be reached
サーバ側のNginxアクセスログを tail -f で監視しても、リクエストが一切到達しません。
しかし、iPhoneのSafariで同じURL(https://<SERVER_IP>/obsidian/)にアクセスすると、証明書エラーなしで表示されます(403 Forbiddenはディレクトリ一覧が無いだけで正常)。
つまり、iOSはmkcertのCA証明書を信頼しているが、Obsidianアプリ内部のHTTPクライアントは信頼していない、という状況です。
試したこと(全て失敗)
iOS版の同期方法として、WebDAV以外にもいくつか試しましたが、全て失敗しました。
- Obsidian Gitプラグイン(iOS版) → isomorphic-gitベースで不安定、clone失敗
- Working Copy(iOS Gitクライアント) → 接続エラー
- GitSync(ネイティブGitクライアント) → Giteaトークン認証問題
- HTTP(非HTTPS)でのWebDAV接続 → iOSのApp Transport Security (ATS) でブロック
つまり、iOS版Obsidianで自宅サーバに接続する方法は、既存の手段では全て行き詰まりました。ここからソースコード解析に入ります。
Step 6: 原因の特定
Remotely Saveのソースコード(TypeScript約3万行)をClaude Codeで解析しました。
リポジトリ: https://github.com/remotely-save/remotely-save
WebDAV通信の仕組み
src/fsWebdav.ts にWebDAV通信のコアがあります。WebDAVライブラリのHTTPリクエストを「パッチャー」でインターセプトし、Obsidianの requestUrl() APIにリダイレクトしています。
// src/fsWebdav.ts(概要)
getPatcher().patch("request", async (options) => {
// ... ヘッダー変換 ...
const resp = await requestUrl({ url: options.url, method: options.method, ... });
// ... レスポンス変換 ...
});
問題の核心
Obsidianの requestUrl() はプラットフォームごとに異なるHTTPスタックを使います。問題はiOSだけ、ユーザーがインストールしたCA証明書を信頼しないことです。
| プラットフォーム | HTTPスタック | ユーザーCA信頼 |
|---|---|---|
| Windows/Mac (Electron) | Node.js / Chromium | ✅ |
| Android | ネイティブHTTP | ✅ |
| iOS | ネイティブHTTP | ❌ |
一方、WKWebViewの fetch() APIはiOSシステム証明書ストア全体を参照するため、ユーザーCAも信頼します。Safariで動くのはこのためです。
SSL関連コードの調査
ソースコード全体を検索しましたが、rejectUnauthorized、NODE_TLS_REJECT_UNAUTHORIZED、証明書ピンニングなどの明示的なSSL設定は一切ありませんでした。SSL検証は完全にプラットフォーム依存です。つまり、プラグインの設定や環境変数では解決できません。
原因が特定できました。iOSの
requestUrl()を迂回して、fetch()を使えば動くはずです。修正に進みます。
Step 7: ソースコードの修正
修正方針
src/fsWebdav.ts のパッチャー内に、iOSの場合のみ fetch() にフォールバックする処理を追加します。iOS以外のプラットフォームには一切影響を与えません。
修正内容
パッチャーコールバック内のヘッダー変換後に、以下のiOS専用分岐を追加しました。
if (Platform.isIosApp) {
const fetchOptions: RequestInit = {
method: options.method,
headers: transformedHeaders,
};
if (
options.data !== undefined &&
options.data !== null &&
options.method.toUpperCase() !== "GET" &&
options.method.toUpperCase() !== "HEAD"
) {
fetchOptions.body = options.data as string | ArrayBuffer;
}
let resp = await fetch(options.url, fetchOptions);
// 既存のiOS向けPROPFIND 401ワークアラウンドを移植
if (
resp.status === 401 &&
!options.url.endsWith("/") &&
!options.url.endsWith(".md") &&
options.method.toUpperCase() === "PROPFIND"
) {
resp = await fetch(`${options.url}/`, fetchOptions);
}
return resp;
}
// iOS以外は従来通り requestUrl() を使用
修正のポイントは以下の通りです。
-
iOS以外には一切影響しない —
Platform.isIosAppがtrueの場合のみ実行されます -
fetch()はResponseオブジェクトを返すので、手動のレスポンスラッピングが不要です - オリジナルコードにあったiOS向けPROPFIND 401ワークアラウンドも
fetch()パスに移植済みです - WebDAVの全メソッド(PROPFIND, MKCOL, PUT, GET, DELETE, LOCK, UNLOCK)は
fetch()の標準仕様でサポートされています
ビルド修正
esbuild.config.mjs にも修正が必要でした。
-
target: "es2016"→"es2020"(依存パッケージのp-queue v8がBigIntリテラルを使用するため) -
node:urlプロトコルimportのshimプラグイン追加
ビルド実行
以下のコマンドで修正版の main.js を生成します。
npm install
npm run build2
約5MBの main.js が生成されます。
修正版プラグインのビルドが完了しました。次はこれをiPhoneに入れます。ここにもう一つハマりポイントがあります。
Step 8: 修正版プラグインのiPhoneへのデプロイ
修正版の main.js をiPhoneのObsidianプラグインフォルダに配置する必要があります。パスは以下の通りです。
このiPhone内/Obsidian/obsidian-vault/.obsidian/plugins/remotely-save/main.js
.obsidianフォルダが見えない問題
しかし、iOSでは以下の制約があり、通常の方法ではファイルを配置できません。
- iOSの**「ファイル」アプリ**では、ドット始まりの隠しフォルダ(
.obsidian)が表示されない -
Apple Devices(iTunes) のファイル共有でも
.obsidianフォルダにアクセスできない場合がある - Documents by Readdle でも、サンドボックス制限で他アプリのフォルダにコピーできない
解決方法: Taioアプリ
Taio(iOS用テキストエディタ/ファイルマネージャ、無料)は、隠しフォルダを含むファイル操作が可能でした。
手順は以下の通りです。
- 修正版
main.jsをzipに圧縮し、WebサーバまたはAirDrop経由でiPhoneに転送します - Taioのファイルブラウザで このiPhone内 → Obsidian → obsidian-vault → .obsidian → plugins → remotely-save に移動します
-
main.jsを上書きコピーします - Obsidianを完全終了(タスクキル)して再起動します
修正版プラグインがiPhoneに入りました。いよいよ動作確認です。
Step 9: 動作確認
iPhoneのObsidianでRemotely Saveの設定を開き、「Check」を押します。
結果: Successful!
Nginxのアクセスログにも、iPhoneからのリクエストが正しく到達していることを確認できました。
"PROPFIND /obsidian/obsidian-vault/ HTTP/1.1" 207 "capacitor://localhost"
"MKCOL /obsidian/obsidian-vault/rs-test-folder-.../ HTTP/1.1" 201 "capacitor://localhost"
"PUT /obsidian/obsidian-vault/.../rs-test-file-... HTTP/1.1" 201 "capacitor://localhost"
"GET /obsidian/obsidian-vault/.../rs-test-file-... HTTP/1.1" 200 "capacitor://localhost"
"DELETE /obsidian/obsidian-vault/.../rs-test-file-... HTTP/1.1" 204 "capacitor://localhost"
PROPFIND, MKCOL, PUT, GET, DELETE — 全てのWebDAVオペレーションが成功しています。Originが capacitor://localhost であることも確認できます。
その後、実際にiPhoneで新しいノートを作成し、Windows PC側で同期を実行したところ、双方向でノートが同期されることを確認しました。
まとめ
解決に必要だった3つのこと
-
Remotely Saveのソースコード修正 — iOSで
requestUrl()の代わりにfetch()を使うフォールバック -
NginxのCORS設定 — Originが
capacitor://localhostであることへの対応 -
Taioアプリ — iOSの
.obsidian隠しフォルダにアクセスしてmain.jsを差し替え
得られた教訓
- iOSの
requestUrl()(Obsidianネイティブ)はユーザーインストールのCA証明書を信頼しません。これはObsidian側の制約であり、プラグインの設定では回避できません - WKWebViewの
fetch()はシステム証明書ストア全体を参照するので、同じiOSでもAPIによって挙動が異なります -
fetch()を使う場合はCORSが強制されます。requestUrl()はCORSをバイパスするので、この差異を意識する必要があります - iOSの隠しフォルダアクセスは「ファイル」アプリでは不可能です。Taioのようなサードパーティアプリが必要です
GitHub
修正コードはForkとして公開しています。また、本家リポジトリにBug Reportを投稿しました。同じ問題で困っている方の参考になれば幸いです。
- 修正版Fork: https://github.com/nabe2030/remotely-save/tree/fix/ios-webdav-fetch-fallback
- 差分: https://github.com/nabe2030/remotely-save/compare/master...fix/ios-webdav-fetch-fallback
- Issue: https://github.com/remotely-save/remotely-save/issues/1158
I am prioritizing self-hosted WebDAV with private CA for data sovereignty and privacy, moving away from public cloud services.