前一阵子帮客户做静态资源迁移方案基本已经定了对象存储用阿里云OSS前端图片、附件全部放上去。本来觉得这事不难结果在“自定义域名”和“CNAME解析”这个环节上消耗了整整两天。客户用的是namesilo注册的域名DNS记录里还历史遗留了好几条CNAME加上对OSS自定义域名的备案要求、HTTPS证书上传方式都不熟悉各种问题叠在一起差点把上线计划拖黄。这篇文章就把这段时间踩过的坑整理出来。内容包括OSS默认域名和自定义域名的区别、CNAME解析原理与多条CNAME的处理方式、域名不在国内服务商时的DNS配置、阿里云OSS绑定自定义域名的完整步骤以及常见的403/NoSuchBucket/HTTPS证书问题怎么排查。最后附带一个把自建MinIO迁移到OSS后如何用自定义域名避免客户端改代码的实战方案。适合正在折腾对象存储迁移、或者第一次给OSS配自定义域名的朋友。1. 搞明白CNAME和OSS自定义域名的各自分工1.1 为什么生产环境要用自定义域名其实刚接触对象存储的人都会有一个疑问OSS不是自带一个访问域名吗形如bucket-name.oss-cn-hangzhou.aliyuncs.com我直接把前端地址写成这个不就行了我在刚开始做第一个小项目时也这么干过确实能访问。但用默认域名做生产环境会遇到几个绕不过去的问题。第一个是预览限制。新版OSS对默认域名的访问策略收得很紧尤其是HTML、XML这类可以直接在浏览器里渲染的内容经常会被强制下载。你正儿八经做了一个图片网站结果用户点开图片直接变成下载体验直接崩掉。绑定自定义域名后OSS才肯把Bucket当“网站”对待。第二个是品牌和可维护性。https://bucket-name.oss-cn-hangzhou.aliyuncs.com这串域名又长又丑写在对外链接里非常不专业将来如果换云厂商这串域名还要跟着变前端代码和用户收藏的地址全都要重新换。换成自己的static.example.com所有这些链接都是长期稳定的。第三个是HTTPS证书。用默认域名时证书是云厂商的统一泛域名证书你没法控制也没法针对自己域名做证书的独立管理。绑定自定义域名后可以上传自己申请的证书也可以申请阿里云免费证书有效期、证书链都能自己掌握。由于现在浏览器对HTTPS越来越严格这一项基本是刚需。第四个是周边生态。默认域名很难和CDN、WAF、防盗链这些能力做深度绑定。虽然技术上也能配但CNAME、证书、缓存这些操作最终都要落到自定义域名上绕一圈不如直接一开始就配好。所以结论非常简单如果你的OSS Bucket要对外提供服务而且不是临时调试一定要绑定自定义域名。1.2 CNAME是如何把自定义域名“接”到OSS的这里需要把CNAME的原理讲清楚。CNAME是DNS系统里的一种记录类型全称是Canonical Name作用是把一个域名“别名”到另一个域名。比如static.example.com配置一条CNAME记录指向bucket-name.oss-cn-hangzhou.aliyuncs.com那么用户访问static.example.com时DNS系统最终解析到的IP就是OSS域名解析出的IP。有人会问为什么不直接配A记录把static.example.com指向OSS服务器的IP原因在于OSS不是一台固定IP的服务器而是一套分布式集群同一地域有多个入口节点IP会随着负载调度和故障切换动态变化。如果配成A记录一旦IP变化用户手里的缓存DNS记录还没过期访问就直接失败。而CNAME指向的是OSS域名域名背后的IP无论怎么变DNS解析都会跟着更新对使用者无感。整个访问链路是这样的用户浏览器输入自定义域名 - 本地DNS递归查询 - 返回自定义域名对应的目标域名地址 - 浏览器请求OSS - 请求里携带的Host头是自定义域名 - OSS根据这个Host头确定要访问的Bucket返回对象。关键就是最后两步。OSS是靠Host头来区分Bucket的。你绑定了static.example.com到某个BucketOSS收到带这个Host头的请求后就知道要去这个Bucket里取数据。如果Host头不在任何Bucket的绑定列表里就会报NoSuchBucket。这个机制在后面排查问题时非常重要。1.3 为什么CNAME比URL转发更合适还有一个常见的坑是“隐形URL转发”。很多人从域名控制台里看到有“URL转发”功能以为只要把static.example.com转发到bucket-name.oss-cn-hangzhou.aliyuncs.com就行了。这个做法和CNAME有本质区别。URL转发是服务器返回一个302跳转用户最终看到的还是OSS默认域名地址栏会变而且对搜索引擎极不友好。CNAME是DNS层面的映射用户全程看不到OSS域名体验上完全是无感的。所以无论你在哪个域名服务商后台都应该添加真正的CNAME记录而不是URL转发。判断标准很简单URL转发通常需要填一个“转发地址”而CNAME填写的是“目标域名/记录值”两者在表单上就不一样。2. 域名解析环节的细节很多人在这里翻车2.1 namesilo这类海外注册商怎么添加CNAME如果你的域名是在namesilo注册的DNS管理入口比较直白登录后进入Manage My Domains找到目标域名的Manage再进入DNS Records标签页。在那里可以添加记录表单里有一项Type选CNAMEHostname填主机记录前缀比如static记录值填CNAME要指向的完整域名例如bucket-name.oss-cn-hangzhou.aliyuncs.comTTL可以先设成600秒解析稳定后再改到3600秒。namesilo自带的DNS服务器有几个特点一是完全免费不额外收解析费二是支持标准的A、AAAA、CNAME、TXT、MX记录基本够用三是默认的TTL最低到600秒切换时不会太难受。它的控制台是英文的但字段含义很直白找不到可以开翻译插件。有一点要提醒如果namesilo域名同时也要在国内使用建议把NS托管到国内或支持按线路解析的云DNS服务上否则国内运营商递归DNS的缓存和解析速度可能不太理想。namesilo自带的DNS节点大多在海外对于国内用户访问阿里云OSS这类的场景解析时延一般不会差太多但个别运营商可能会有解析验证问题遇到再说。2.2 免费DNS服务怎么选Cloudflare应该开代理吗现在免费DNS服务很多我主要用过三类Cloudflare、阿里云云解析DNS、DNSPod。它们的免费套餐都能满足CNAME解析需求区别在于附加能力。Cloudflare强在免费版还有CDN和WAF但用在OSS自定义域名场景下有个大坑默认开启的“橙色云朵”代理会将用户请求先到Cloudflare边缘节点再由Cloudflare去源站取数据。如果你在OSS里配置了防盗链、Referer白名单或私有Bucket签名Cloudflare的代理IP可能不在白名单里导致每次回源都403。所以如果你用Cloudflare来管理域名请把DNS记录设置为“仅DNS”模式也就是灰色云朵。这样Cloudflare只做解析不拦截请求CNAME还是CNAME请求直接由DNS解析结果指向OSS最干净。如果你的目标是加速不要靠Cloudflare代理而是去用阿里云CDN后面会单独讲。阿里云云解析DNS和DNSPod更符合国内使用习惯尤其是阿里云云解析可以按“解析线路”区分返回不同的记录值。如果你在阿里云有备案过的域名直接在云解析里加CNAME最方便它和OSS控制台配合同步提示做得比较好。DNS服务CNAME支持免费版特点在OSS场景的注意点namesilo自带DNS支持标准记录海外节点界面英文无线路区分Cloudflare支持附带CDN/WAF必须关掉代理用灰色云朵阿里云云解析支持国内解析快支持线路需要阿里云账号可自动关联DNSPod支持老牌免费版够用与腾讯云相关注意记录冲突2.3 一个域名到底能配几条CNAME这个问题看上去基础但“域名是多条CNAME”这个热词恰恰说明很多人在这儿踩了坑。严格来说DNS协议标准规定一个主机记录不能同时存在多条CNAME记录CNAME也不能和这个主机记录的A记录、AAAA记录、MX记录共存。注意这里说的是“同一个主机记录”。比如static.example.com只能有一条CNAME目标要么是OSS域名要么是CDN域名不能同时存在两条。那为什么还会有人遇到“多条CNAME”的情况呢常见的有三种。第一种是多个子域名各自有一条CNAME比如static.example.com、files.example.com、img.example.com各指向不同的目标这完全合法。第二种是某些国内DNS服务商支持“解析线路”功能按移动、联通、电信或者区域返回不同值从管理界面上看同一个主机记录对应两条CNAME但这本质上是条件解析只有在特定线路时才返回对应值默认线路必须设置成指向OSS的那条。第三种就是配置错误旧记录没删除新记录互相冲突解析结果随机这是最危险的。在OSS绑定场景下的建议是一个自定义域名对应一条CNAMECNAME目标要么是OSS域名要么是CDN加速域名不要搞成多条互指。如果你发现同一主机记录下有一条旧CNAME先确认旧记录是否还在使用不用就直接删掉再新增。如果确实需要一套域名同时服务多个目标用不同的子域名去隔离而不是在同一主机记录上硬堆。3. 阿里云OSS绑定自定义域名的实操过程3.1 前置检查备案、Bucket区域和域名归属在开始配置之前先把三个前提条件确认好否则步骤全做完也可能白搭。第一个是备案。域名要绑定中国大陆地域的OSS Bucket必须已经完成ICP备案并且备案接入商是阿里云。如果你用海外区域比如新加坡Region原则上不需要备案但国内访问延迟会明显偏高。实际操作中大量静态资源网站选择的是国内Bucket自定义域名所以备案这一步一定不要忘。备案一般需要几天别等到上线前一天才想起来。第二个是Bucket区域。CNAME目标域名里带Region标志比如bucket-name.oss-cn-beijing.aliyuncs.com。你的Bucket在哪个区域CNAME就必须指向哪个区域的访问域名。跨区域互指会直接导致NoSuchBucket。这个在后面排查部分还会重点说。第三个是域名所有权。你要能操作该域名的DNS记录或者至少能拿到DNS管理权限。很多人给公司配OSS找行政要域名权限要了一圈最后发现域名在集团另一条线手里。所以动手前先确认CNAME记录能不能加必要时先在DNS里加一条TXT记录做归属验证。不少云平台在绑定自定义域名时会要求通过DNS验证或文件验证来确认域名归你OSS也在逐步收紧这一步按控制台提示操作即可。3.2 一步一步完成绑定和CNAME解析以阿里云控制台为例完整流程如下登录OSS控制台进入Bucket列表打开你准备使用的Bucket在左侧菜单找到“传输管理”下面的“域名管理”。点击“绑定域名”输入你的自定义域名比如static.example.com可以先不勾选HTTP/2和HTTPS配置等解析通过后再补证书。控制台保存后会显示一条CNAME解析目标通常就是该Bucket的默认访问域名。记下这个值。去你的DNS服务商后台添加一条CNAME记录主机记录填static记录值填刚才控制台给的OSS域名TTL建议先填600秒。回到OSS控制台的域名管理页面等待状态变为“已生效”或“正常”。这个过程一般几分钟但如果本地DNS缓存严重可能需要更久。生效后先用命令行或浏览器做一次访问验证确保对象能正常打开。这里有一个容易搞反的顺序有人先在DNS里加CNAME再去OSS绑定域名发现一直提示“请求被拒绝”或“域名不在许可列表”。其实正确顺序是先添加自定义域名再配CNAME。你可能会问为什么不能先配CNAME因为OSS在做域名绑定时会先检查这个域名是否已经有过绑定或是否在其他账号的绑定列表里。如果该域名还在别的账号下绑定过那就需要先做域名归属验证通常是上传一个验证文件到自己的源站或DNS解析TXT记录验证通过后才会允许绑定。所以流程上按控制台提示走不要跳步。3.3 开启HTTPS并配置证书自定义域名解析生效之后紧接着就要解决HTTPS。直接访问http://static.example.com当然能用但现代浏览器和业务环境都已经默认要求HTTPS所以证书这一步躲不掉。在OSS控制台的域名管理列表中找到你的自定义域名旁边会有“上传证书”或“配置HTTPS证书”的入口。你需要准备好一个匹配域名static.example.com的SSL证书。选择有三条路一是用阿里云数字证书管理服务申请免费证书二是在自己的服务器上用ACME工具申请Lets Encrypt证书三是购买商业证书。对于一般场景云厂商免费证书完全够用注意申请时填对域名并开启自动续期。上传证书时有个常见错误很多人只把“服务器证书”文件传上去忽略“证书链”文件。如果你的证书不是由阿里云签发而是第三方证书通常需要把服务器证书和中间证书拼接成一个文件后上传。否则HTTPS访问时部分客户端尤其是手机会报证书链不完整。解决办法是把签名后的crt和中间证书内容按顺序拼接重新上传。开启HTTPS后建议同时打开“HTTP跳转到HTTPS”开关避免用户从旧链接进来时拿到明文内容。这里要注意如果你还打算接CDNCDN那边也要独立配置证书而不是只配OSS。两者的证书配置是分开的。3.4 静态网站托管和权限设置绑定自定义域名的目标是让Bucket像网站一样被访问那就绕不开两个配置静态页面和Bucket权限。如果你希望用户访问http://static.example.com/时直接看到首页而不是收到错误需要到Bucket的“基础设置”-“静态页面”里开启静态网站托管。默认首页通常填index.html404页面可以填error.html。这一步不开启的话根路径访问会返回404或XML错误。注意开启静态网站托管后Bucket默认就对匿名用户开放读取了如果Bucket里还有敏感数据请谨慎开启。权限方面常见的做法是“Bucket公共读 静态网站托管”。这样自定义域名下的所有对象都可以被公开访问服务端也不需要做签名。但如果你做的是私有文件分享比如用户上传的私有附件那么Bucket要保持私有读写对外访问必须用签名URL。绑定自定义域名后私有Bucket同样可以用https://static.example.com/object?Signaturexxx的格式生成临时访问链接只是签名过程要在服务端完成。很多开发者在私有Bucket下直接访问对象看到一个AccessDenied就以为绑定失败了其实只是权限配置的问题把权限改成公共读或者用签名URL就好了。4. 实战中高频出现的异常问题和排查4.1 绑定后提示“解析未生效”怎么办这种提示通常出现在刚刚添加CNAME记录后控制台状态一直显示“等待解析”或“解析未生效”。先别急着删除记录用下面的顺序排查在本地执行dig static.example.com CNAME看输出的ANSWER SECTION里是否有CNAME记录以及目标是否和你配置的一致。如果本地没有安装dig用nslookup -typeCNAME static.example.com 8.8.8.8绕开本地DNS直接查询公共DNS。dig static.example.com CNAME nslookup -typeCNAME static.example.com 8.8.8.8如果dig结果里没有CNAME说明DNS记录还没有同步到公共递归服务器。原因可能是TTL太短但对方缓存太长也可能是你配置的DNS服务商不是域名的权威DNS。比如域名NS还在namesilo但你跑到阿里云云解析里添加记录是不会生效的。检查域名当前的NS记录dig static.example.com NS还有一种情况是CNAME记录加了但被同一主机记录的A记录或旧CNAME覆盖了。用dig static.example.com ANY看所有记录如果同时存在两条冲突记录说明问题就出在“多条CNAME”这个坑上。把旧的冲突记录删掉只保留一条CNAME。4.2 访问返回403、AccessDenied、NoSuchBucket这是绑定自定义域名后最常遇到的一类问题。我排查过不下十次基本集中在下面几个原因。报错最常见原因解决办法403 AccessDeniedBucket是私有权限匿名请求没有签名改公共读或用签名URL/STS403 AccessDenied防盗链规则拦截了非白名单Referer检查防盗链白名单临时关掉测试NoSuchBucketCNAME指向的Bucket域名写错或Region不对去Bucket概览页复制默认域名重新配置NoSuchBucket该自定义域名没有绑定到任何Bucket确认OSS控制台绑定状态已生效404 NoSuchKey对象不存在或路径大小写不对通过OSS控制台确认对象路径用curl测直链其中NoSuchBucket最容易让人头疼因为DNS解析明明成功了但对象就是访问不到。其实根源在于OSS通过Host头判断Bucket。你的自定义域名CNAME到了blog-img.oss-cn-hangzhou.aliyuncs.com但OSS内部没有把static.example.com和blog-img这个Bucket关联起来就会认为这个Host找不到对应Bucket。检查一下Bucket名称和自定义域名的绑定关系确认控制台里已经绑定的是同一个域名同时确认Region和CNAME目标一致。403的排查相对简单可以先临时把Bucket权限改成公共读关掉防盗链再试一次。如果恢复正常说明是权限或防盗链配置问题。这里分享一个习惯每次调整完权限相关配置后用无痕浏览器窗口访问一次避免本地浏览器缓存干扰判断。4.3 HTTPS证书报错排查HTTPS开启后最常见的报错是浏览器提示证书不可信、证书域名不匹配或证书链不完整。域名不匹配比较直接证书的CN或SAN里必须包含static.example.com如果你上传了一个blog.example.com的证书那肯定报错。多域名证书也支持比如UCC证书可以包含多个域名只要SAN列表里有当前访问域名就行。证书链不完整在OSS上表现得很隐蔽电脑Chrome能打开但手机浏览器报NET::ERR_CERT_AUTHORITY_INVALID。这是因为你只上传了叶子证书服务器证书没有上传中间证书。OSS在TLS握手时返回给客户端的证书链不完整客户端找不到信任锚点。解决办法是把服务器证书和中间证书按“服务器证书在前中间证书在后”的顺序拼接成一个文件重新上传。如果你用的阿里云免费证书通常控制台会提供拼接好的证书链文件直接下载上传即可。如果上传后还没生效可以在OSS域名管理页操作“重新验证”或等待几分钟。不要反复强制刷新导致旧证书缓存。4.4 接CDN加速后CNAME目标变了很多人在配完OSS自定义域名后觉得访问速度还可以但流量一大就想着接CDN。这一接CNAME的指向就变了原本static.example.com的CNAME直接指向OSS域名接CDN后需要先到CDN控制台添加加速域名CDN会分配一个形如static.example.com.w.cdngslb.com的CNAME地址然后把DNS里的CNAME改成指向这个CDN地址。CDN回源时再通过源站配置去OSS取数据。这里有三个点要提前注意。第一使用CDN加速域名域名同样需要备案中国大陆地域而且CDN本身也要绑定证书。第二CDN回源到OSS时源站地址填OSS的Bucket默认域名CDN控制台给你一个Cache回源配置建议开启“回源HOST”设置成你的Bucket域名或OSS默认域名否则可能会碰到回源403。第三OSS上已经绑定的自定义域名不需要删除但此时它和CDN是双层关系如果你在OSS域名管理里开启HTTPS、又在CDN里开启HTTPS回调链路会多一层证书验证配置时要理清。CDN和直连OSS的取舍很简单访问量不大、对实时性要求高、不想引入缓存一致性问题的直接CNAME到OSS就够了访问量大、需要全国加速、想要WAF和DDoS防护的就上CDN把CNAME指向CDN。两者不在一个维度上并不是所有场景都必须上CDN。5. 场景延伸MinIO迁移到OSS后怎么平滑接自定义域名5.1 为什么要从MinIO迁到OSS很多团队早期为了省钱自建MinIO做对象存储。我在几个项目里也用过MinIO部署简单、兼容S3 API其实挺顺手。但跑到一定规模后自建集群的运维成本开始明显上升硬盘坏了要换版本要升级纠删码配置要调备份策略要自己设计真要达到高可用至少得起三台机器。这时候迁到OSS这种托管对象存储就变成了一个很自然的选择按量付费不用看机器容量理论无限自带多副本和跨域容灾。但迁移本身并不只是把文件拷贝过去那么简单。最大的痛点是业务代码里已经写死了MinIO的访问域名和端口比如http://minio.example.com:9000。如果直接换成OSS默认域名所有调用方都要改配置甚至要改代码。自定义域名在这里就起到了一个“替身”作用。5.2 用rclone/ossutil做全量迁移迁移工具我用过rclone和ossutil两者各有适合的场景。rclone更适合从MinIO到OSS的跨源同步因为MinIO本身就是S3兼容服务rclone可以同时把MinIO和OSS都当成S3协议的remote来配置。配置rclone比较简单配置文件里建两个remote。一个指向MinIOtype为s3provider为Minioendpoint填http://minio.example.com:9000access_key和secret_key填MinIO的管理员密钥。另一个指向OSStype为s3provider为Aliyunendpoint填https://oss-cn-hangzhou.aliyuncs.comaccess_key和secret_key填阿里云RAM子账号密钥。两个remote配置好后执行类似下面的命令就能把数据同步过去rclone sync minio:mydata oss:mydata --progress --transfers 16 --checkers 16同步前建议先跑rclone lsd minio:和rclone lsd oss:确认两个remote下的Bucket都能正确列出。同步过程中如果想减少风险可以先用--dry-run预览操作再正式执行。对象数量很大的话注意系统磁盘空间rclone的临时下载空间会占用一部分磁盘。如果是小规模迁移用阿里云ossutil也可以。比如ossutil sync minio://mydata oss://mydata -r -u。但ossutil原生要识别MinIO需要点额外配置不如rclone直连S3干净。5.3 迁移切换时自定义域名怎么改才能不让客户端感知数据迁移完成后最理想的情况是业务侧只需要把AccessKey/SecretKey从MinIO的换成阿里云RAM的其他地址不用动。这可以通过自定义域名CNAME来实现。第一步在OSS里新建一个Bucket也可以沿用之前迁移的目标Bucket绑定原MinIO所服务的存储域名minio.example.com并在DNS中把minio.example.com的CNAME指向这个Bucket的OSS默认域名。第二步由于原MinIO的endpoint是带端口:9000的OSS的对外监听只有80/443所以业务配置里要去掉端口如果原来有http://minio.example.com:9000改成https://minio.example.com或http://minio.example.com。第三步确认客户端使用的是S3协议的path-style模式也就是请求路径为/bucket/keyHost为minio.example.com这样OSS就能通过自定义域名绑定关系定位到正确的Bucket。这里有个容易忽略的细节S3 SDK默认是virtual-host style会把Bucket名加到域名前面变成mydata.minio.example.com。如果你没有为这个子域名再配一条CNAME就会解析失败。所以在客户端里要显式开启path-style访问或者干脆用OSS提供的S3兼容endpoint并在应用层更新endpoint配置。换地址和改配置总有一个要发生如果你确实想做到完全无感就用path-style自定义域名这一套。另外如果原MinIO域名同时还要保留给MinIO管理后台使用建议提前划分子域名。例如文件流量的域名是files.minio.example.com管理台还是minio.example.com两者分开。这样迁移时只管给files这个子域名配CNAME到OSS管理后台不用动。多条CNAME在这个场景下反而是最佳实践。5.4 迁移后的验证清单迁移完成后不要急着把旧MinIO关机。先用业务账号完整跑一轮读写下载一个对象、上传一个新对象、通过签名URL访问一次私有对象。特别要注意私有Bucket在自定义域名下返回的签名URL其签名算法和MinIO可能不同但OSS支持S3签名格式多数语言SDK不需要改签名逻辑。如果遇到签名问题优先检查客户端使用的Endpoint是否和Bucket所在Region一致其次检查系统时间是不是偏差过大S3签名对时间偏差很敏感。还要检查对象元数据。MinIO里有些对象的Content-Type可能设置得不太标准迁移到OSS后rclone默认会把元数据一起复制但如果你中途用控制台或脚本改过元数据最好抽样几个对象用curl -I比较响应头里的Content-Type、Content-Disposition确保下载行为一致。我遇到过迁移后图片文件全部变成application/octet-stream的情况就是因为MinIO之前的存储策略没有保留正确的Content-Type好在OSS里可以通过批量修改元数据或重传源文件解决。最后说一个我自己的习惯每次配自定义域名我都会把TTL先调到600秒等解析稳定了再调回3600秒。这样如果配置过程中发现CNAME指向错了还有机会快速修正。之前在namesilo上踩过一条教训改完TTL后等了一个多小时排查最后发现是本地电脑的DNS缓存还在解析旧值后来直接用curl --resolve static.example.com:443:OSS-IP https://static.example.com指定Host验证后端绕开本地DNS效率高很多。这套方法在OSS、CDN、MinIO迁移场景里都适用你可以直接拿去用。