お久しぶりです。
またですが、今回も n8n の話です。
n8n を使って一時的なコンテンツサーバを立てる方法についてのメモです。
この話をする理由
n8n のワークフローを作っているときに、
「外部で受け取ったファイルを一時的に保存して、後でダウンロードできるようにしておきたい」と思いました。
コンテンツを公開するためにコンテンツサーバーを立てるべきかと考えましたが、一時的な用途のためにわざわざ Docker を準備するのは面倒だと思いました。
なんとか n8n でできないかなぁと思い、chatGPT と壁打ちしていたところ、良い方法が見つかりました。
中身に入る前に大前提
Docker で n8n を動かしていることを前提に話を進めます。
n8n のインストール方法は公式ドキュメントを参照してください。
n8n のバージョン
Version 1.103.2 で確認しています。
また、セルフホスト版を使っています。
やりたいこと
GET リクエストでファイルをダウンロードしたい。
可能であれば、簡易的なセキュリティも付けたいです。
さらに欲を言えば、POST リクエストでアップロードもできないか。
できたもの
ということで、できたものがこちらになります。
Webhook を使って実現しました。

中身はとても単純です。
https://${YOUR_HOST}/webhook/file に POST リクエストを送ると、ファイルがサーバーに保存されます。
# e.g.
curl -X POST https://${YOUR_HOST}/webhook/file \
-F "file=@/path/to/image.png"
https://${YOUR_HOST}/webhook/file?filename=${FILE_NAME} に GET リクエストを送ると、保存したファイルをダウンロードできます。
# e.g.
wget "https://${YOUR_HOST}/webhook/file?filename=image.png"
セキュリティ
n8n の Webhook ノードは優秀で、Basic Auth や IP 制限などを設定できます。
好みに応じて設定してください。
| セキュリティ設定1 | セキュリティ設定2 |
|---|---|
![]() | ![]() |
コード
よろしければ参考にしてみてください。
{
"name": "sample",
"nodes": [
{
"parameters": {
"path": "file",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-416,
16
],
"id": "05edf4d6-ec31-4ccc-99b2-55732ed974ae",
"name": "Get Webhook",
"webhookId": "bd6aeee2-b26d-4b2a-a2aa-e4063d1f5d5b"
},
{
"parameters": {
"respondWith": "binary",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
288,
16
],
"id": "445987d3-83d4-49d2-8ffd-c5fadc2cb93b",
"name": "Response",
"alwaysOutputData": false
},
{
"parameters": {
"httpMethod": "POST",
"path": "file",
"responseMode": "responseNode",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-416,
-144
],
"id": "a943b130-6f49-4afd-b4d3-d5c432fd0583",
"name": "Post Webhook",
"webhookId": "bd6aeee2-b26d-4b2a-a2aa-e4063d1f5d5b"
},
{
"parameters": {
"fileSelector": "=/tmp/{{ $json.filename }}",
"options": {
"fileName": "={{ $json.filename }}",
"mimeType": "={{ $json.mimeType }}"
}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1,
"position": [
48,
16
],
"id": "50eb78a6-2f60-49f0-9712-be56ce14597e",
"name": "Read File From Tmp Folder."
},
{
"parameters": {
"operation": "write",
"fileName": "=/tmp/{{ $json.fileName }}",
"dataPropertyName": "file",
"options": {}
},
"type": "n8n-nodes-base.readWriteFile",
"typeVersion": 1,
"position": [
48,
-144
],
"id": "c6b46191-38ed-464f-9be1-cd73a00cbd63",
"name": "Read/Write Files from Disk"
},
{
"parameters": {
"jsCode": "const fileName = $input.first().binary.file.fileName;\n\nreturn [\n {\n json: {\n fileName\n },\n binary: $input.first().binary\n }\n]"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-176,
-144
],
"id": "94c1ac5b-d4bb-41e3-a6dc-4f7c0d69475b",
"name": "Get File Name"
},
{
"parameters": {
"jsCode": "const filename = $input.first().json.query.filename || \"default.jpg\";\nconst ext = filename.split('.').pop().toLowerCase();\nconst mimeMap = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n};\nconst mimeType = mimeMap[ext] || \"application/octet-stream\";\nreturn [{ json: { filename, mimeType } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-176,
16
],
"id": "3b2933d1-b904-4993-b1a6-fe61b71f3cc1",
"name": "Get File Info"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"messsage\": \"success\"\n}",
"options": {}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.4,
"position": [
288,
-144
],
"id": "6637f6e7-4738-4c1c-ad54-4832f52bd844",
"name": "Response1"
}
],
"pinData": {},
"connections": {
"Get Webhook": {
"main": [
[
{
"node": "Get File Info",
"type": "main",
"index": 0
}
]
]
},
"Read File From Tmp Folder.": {
"main": [
[
{
"node": "Response",
"type": "main",
"index": 0
}
]
]
},
"Post Webhook": {
"main": [
[
{
"node": "Get File Name",
"type": "main",
"index": 0
}
]
]
},
"Read/Write Files from Disk": {
"main": [
[
{
"node": "Response1",
"type": "main",
"index": 0
}
]
]
},
"Get File Name": {
"main": [
[
{
"node": "Read/Write Files from Disk",
"type": "main",
"index": 0
}
]
]
},
"Get File Info": {
"main": [
[
{
"node": "Read File From Tmp Folder.",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "d20798f2-748c-4887-a83e-33ccc41948aa",
"meta": {
"instanceId": "f1ea10e2477ea0ca0966ce19137d7a6d60d042c865a530a9bc6a8f603af2cc15"
},
"id": "A2Knxh6bn6YQp5QD",
"tags": []
}
一応注意
主に上記のコードをそのまま使う方への注意点です。
ファイルの保存先
ファイルのアップロード先が /tmp になっています。
そのため、n8n のコンテナが再起動されるとファイルは消えます。
一時的なコンテンツサーバとして使うことを想定しているので、特に問題はないと思いますが、必要に応じて変更してください。
tmpフォルダには他の一時ファイルも含まれる
/tmp フォルダ内にはさまざまなログファイルが生成されています。
GETリクエストによって、意図しないファイルがダウンロードされる可能性があります。
そのため、Check Extension ノードを使って、ファイルの拡張子を制限するなどの対策を推奨します。
例えば、以下のようになります。
| 全体像 |
|---|
![]() |
| Check Extension の設定 |
|---|
| 画像ファイルのみを許可する場合 |
![]() |
あとは、セキュリティの設定をしておけば、ある程度は安心して使えると思います。
まとめ
n8n を使って一時的なコンテンツサーバを立てる方法についてのメモでした。
n8n の Webhook ノードを使うことで、簡単に一時的なファイルサーバを構築できることがわかりました。
これを使えば、Docker を準備することなく、手軽に一時的なコンテンツサーバを立てることができます。
もし、n8n を使っている方で一時的なコンテンツサーバを立てたいと思っている方がいれば、ぜひ試してみてください。



