自建图床指南

October 08, 2021

s3-nginx-cloudflare

图片托管是一个很常见的需求,经常能看到不少人在想方设法使用类似微博图床、GitHub issues 等方案变相的当作免费图床,实际上这些方法很容易违反对方的服务条款,说不准哪天就把你的图片删了或者限制外链到其他网站访问,这种是非常不推荐的。

本文用 Amazon S3 和 Cloudflare 举例如何基于对象存储服务和 CDN 自建一个可靠的低成本静态文件托管服务。

这套方案的核心是利用 S3 这类对象存储服务解决文件存储的可靠性和安全性,再利用 Nginx 反向代理和缓存,配合 CDN 极大降低流量成本。

前置条件

本文不是面向完全新手的,需要你有一定的相关经验

  • 在 S3 开通一个 Bucket (S3 也可以替换为其他对象存储服务,原理是一样的)
  • 一个域名(用于访问你的文件)
  • 一台安装好 Nginx 的的服务器
  • 开通并配置好 Cloudflare(可选,也可以用其他 CDN 厂商代替)

在前置条件全部准备好之后,参考如下 Nginx 配置,配置好反向代理和缓存。

Nginx 配置

# 先创建缓存目录,并设置正确权限,确保 nginx 有权限读写
mkdir -m 777 /var/cache/nginx
http {
  # 最多缓存 1000M, 缓存时间 30天
  proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=s3_cache:100m max_size=1000m inactive=30d;

  server {
    listen 443;
    server_name your_domain; # 替换成你自己的域名

    # s3 资源反向代理
    location ~ ^/resource/(.+)$ {
      proxy_hide_header      x-amz-id-2;
      proxy_hide_header      x-amz-request-id;
      proxy_hide_header      x-amz-meta-server-side-encryption;
      proxy_hide_header      x-amz-server-side-encryption;
      proxy_hide_header      Set-Cookie;
      proxy_ignore_headers   Set-Cookie;
      proxy_set_header       Connection "";
      proxy_set_header       Authorization "";
      proxy_set_header       Host your_bucket_name.s3.eu-west-2.amazonaws.com;

      proxy_cache            s3_cache;
      # 如果源站响应状态码大于 300, 则返回 nginx 自己的错误页面,避免泄露源站信息
      proxy_intercept_errors on;
      proxy_cache_revalidate on;
      proxy_cache_use_stale  error timeout updating http_500 http_502 http_503 http_504;
      proxy_cache_lock       on;
      # 状态码为 200 则缓存 30 天
      proxy_cache_valid      200 30d;
      # 用户浏览器和CDN都缓存 365 天
      add_header             Cache-Control "max-age=31536000";
      add_header             X-Cache-Status $upstream_cache_status;

      proxy_pass https://your_bucket.s3.eu-west-2.amazonaws.com/$1;
    }
  }
}

上面配置文件中的 your_bucket.s3.eu-west-2.amazonaws.com 需要替换成你自己的 S3 Endpoint

配置文件保存后执行 nginx -t 可以检查语法是否正确,如果没问题,执行 service nginx reload 让配置生效。

Nginx 和 Cloudflare 都配置完成之后,通过你的域名访问 S3 上的资源会有两层缓存,大部分请求会被 CDN 和 Nginx 缓存拦截下来,不会回源到 S3 源站,所以你只需要支付 S3 的存储费用和少量流量费用,只要你的流量不是太夸张,Cloudflare 默认不会收取你的流量费用。

注意,本文这里设置缓存日期是 30 天,对于大部分情况下是没问题的,如果你希望 S3 的文件删除后,后续请求不返回缓存的文件,那么就要自己再做一些定制了。

安全问题

使用 S3 通常都是按实际使用流量付费的,如果你不想一觉醒来收到天价账单,就需要做一些防止刷流量的措施,防范于未然,常用的措施有以下几种:

  • 防盗链,就是只允许指定的网站访问你的文件,避免被滥用产生天价流量费,一般 CDN 都支持防盗链配置
  • 防止绕过缓存,你需要避免源站 IP 和 S3 相关配置泄露,比如 Bucket 和 Endpoint,避免攻击者绕过缓存恶意刷流量,必要时你可以在 S3 控制台配置安全策略,只允许你自己的服务器 IP 访问

S3 安全配置

默认情况下如果别人知道了你的 Bucket 名称和 Endpoint 地址是可以直接绕过 CDN 直接访问 S3 源站的,这样就有被刷流量的风险,我们可以使用 S3 的存储桶策略来增强安全性。

具体配置路径是:进入 S3 控制台 -> 选择 Bucket -> 点击 权限 Tab -> 下拉找到 存储桶策略

s3-console-policy

s3-policy-config-json

点击编辑按钮,参考下面的配置,可以将 aws:Referer 设置成一个随机生成的字符串,这样后续访问 S3 如果没有携带这个 Referer 值就会被 S3 拦截

{
  "Version": "2012-10-17",
  "Id": "Policy1633794210209",
  "Statement": [
    {
      "Sid": "只允许指定的 Referer 访问公共资源",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": ["arn:aws:s3:::your_bucket/dir1/*", "arn:aws:s3:::your_bucket/dir2/*"],
      "Condition": {
        "StringNotEquals": {
          "aws:Referer": "替换成任意字符串,你可以随机生成一个ID放在这里"
        }
      }
    }
  ]
}

上面的 Resource 请替换成你自己的 Bucket 名称和目录

S3 安全策略配置完成后,需要在 Nginx 反向代理配置中用 proxy_set_header 指令加上 Referer 请求头,这样只有通过你的 Nginx 服务器才能访问 S3 的公共资源

    location ~ ^/resource/(.+)$ {
      ...
      
      proxy_set_header Referer "替换成S3存储桶策略里填写的 Referer 值";
      
      ...
      
      proxy_pass https://your_bucket_name.s3.eu-west-2.amazonaws.com/$1;
    }

相关链接

文件上传

要将文件上传到 S3,通常有以下几种方法:

  • 使用 s3 提供的各种 sdk 进行上传(需要写代码,可定制性强)
  • 使用第三方的上传工具,比如开源的 uPic (因为要配置密钥,所以只适合自己或者小范围使用)

uPic-config

如果你想实现自己的上传逻辑,可以参考:

FAQ

Q: 上传到 S3 的文件无法访问提示没权限
A: 请检查对应文件的访问权限是否设置正确

s3-acl.jpg

其他更好的方案?

最近(2021-10)Cloudflare 接连推出 Cloudflare ImagesCloudflare R2 Storage这两个服务,如果你是 Cloudflare 重度用户,可以研究下这两个服务,官方说法是相比传统方案它可以消除数据在不同服务商之间流转造成的大量流量出口费用。

cloudflare-r2-vs-s3

相关资料

如果你喜欢我的内容,请考虑请我喝杯咖啡☕吧,非常感谢🥰 。

If you like my contents, please support me via BuyMeCoffee, Thanks a lot.