Visão geralOverview
A Cloudflare entra em três papéis neste desenho: registrar do domínio e DNS, CDN e proxy na frente da sua aplicação, e storage de arquivos (R2). A AWS continua rodando a aplicação atrás de um ALB. O objetivo é que toda a viagem seja HTTPS.Cloudflare plays three roles in this layout: domain registrar and DNS, a CDN and proxy in front of your application, and file storage (R2). AWS keeps running the application behind an ALB. The goal is for the whole trip to be HTTPS.
- Navegador → Cloudflare: o navegador abre
https://app.exemplo.com. A Cloudflare responde com o certificado de borda (Universal SSL).Browser → Cloudflare: the browser openshttps://app.example.com. Cloudflare answers with its edge certificate (Universal SSL). - Cloudflare → ALB: com o modo Full (strict), a Cloudflare abre uma conexão HTTPS com o ALB e valida o certificado de origem que você instalou nele.Cloudflare → ALB: with Full (strict) mode, Cloudflare opens an HTTPS connection to the ALB and validates the origin certificate you installed on it.
- Arquivos: o navegador baixa e envia mídia direto do R2, por URL pré-assinada (bucket privado) ou por um domínio público com CDN (bucket público).Files: the browser downloads and uploads media straight from R2, through a presigned URL (private bucket) or a public custom domain with CDN (public bucket).
1. Comprar um domínio1. Buy a domain
No painel da Cloudflare: Register domains. O caminho é:In the Cloudflare dashboard: Register domains. The path is:
- Pesquise o nome ou palavras-chave. Domínios que não aparecem nas sugestões não estão disponíveis.Search for a name or keywords. Domains missing from the suggestions are unavailable.
- Clique em Purchase; a Cloudflare faz uma última checagem de disponibilidade.Click Purchase; Cloudflare runs a final availability check.
- Escolha o prazo (a maioria das TLDs vai até dez anos). A renovação automática vem ligada e pode ser desligada depois.Pick the term (most TLDs go up to ten years). Auto-renew is on by default and can be turned off later.
- Preencha os contatos (registrante, administrativo, técnico e cobrança) e a forma de pagamento.Fill in the contacts (registrant, admin, technical and billing) and the payment method.
- Revise os contratos e clique em Complete purchase. O registro leva até cerca de 30 segundos.Review the agreements and click Complete purchase. Registration takes up to about 30 seconds.
Requisitos e pegadinhasRequirements and gotchas
- Precisa de um e-mail verificado na conta e de uma forma de pagamento.You need a verified email on the account and a payment method.
- Os contatos aceitam só caracteres ASCII, e devem ser completos e corretos: dados inexatos podem levar à suspensão ou cancelamento.Contacts accept ASCII characters only, and must be complete and accurate: inaccurate details can lead to suspension or cancellation.
- Verifique o e-mail do registrante. Se a verificação ficar pendente ou expirar, a ICANN coloca o domínio em hold e os nameservers viram servidores de estacionamento até a verificação ser concluída.Verify the registrant email. If verification stays pending or expires, ICANN places a hold on the domain and the nameservers are replaced with parking nameservers until verification is completed.
- Domínios registrados lá usam os nameservers da Cloudflare, e não é possível trocá-los por outro provedor de DNS enquanto estiver no Registrar.Domains registered there use Cloudflare nameservers, and you cannot switch to another DNS provider’s nameservers while using Registrar.
- Nomes internacionalizados (IDN, com acentos) não são suportados. A Cloudflare recomenda ligar o DNSSEC.Internationalised domain names (IDNs, with accents) are not supported. Cloudflare recommends enabling DNSSEC.
2. Configurar o domínio e o DNS2. Configure the domain and DNS
Se você comprou pela Cloudflare, o domínio já usa os nameservers dela. Se comprou em outro registrador, adicione-o à Cloudflare e troque os nameservers lá.If you bought through Cloudflare, the domain already uses its nameservers. If you bought somewhere else, add it to Cloudflare and change the nameservers at your registrar.
Domínio vindo de outro registradorDomain from another registrar
- Em Domains → Onboard a domain, informe o domínio raiz (por exemplo
exemplo.com; subdomínios não servem) e escolha um plano.In Domains → Onboard a domain, enter the apex domain (for exampleexample.com; subdomains do not work) and pick a plan. - Revise os registros DNS. O scan automático pode não achar todos: confira o registro da raiz, o
wwwe os de e-mail (MX, SPF, DKIM, DMARC).Review the DNS records. The automatic scan may not find every one: check the apex record,wwwand the email ones (MX, SPF, DKIM, DMARC). - A Cloudflare indica dois nameservers. No registrador, troque os atuais por esses dois.Cloudflare assigns two nameservers. At your registrar, replace the current ones with them.
- Aguarde a ativação (um domínio novo pode levar até 24 horas para ser verificado). Depois disso a Cloudflare passa a ser seu DNS e proxy reverso.Wait for activation (a newly added domain can take up to 24 hours to be verified). After that, Cloudflare is your DNS and reverse proxy.
Proxy ligado ou desligadoProxied or DNS only
Cada registro A, AAAA ou CNAME tem um status de proxy:Each A, AAAA or CNAME record has a proxy status:
| StatusStatus | O que aconteceWhat happens |
|---|---|
| Proxied (nuvem laranja)Proxied (orange cloud) | O tráfego HTTP/HTTPS passa pela Cloudflare: CDN, cache, WAF, proteção DDoS. O DNS devolve IPs anycast da Cloudflare, e não o IP da sua origem.HTTP/HTTPS traffic goes through Cloudflare: CDN, cache, WAF, DDoS protection. DNS returns Cloudflare anycast IPs, not your origin IP. |
| DNS only (nuvem cinza)DNS only (grey cloud) | O DNS devolve o endereço real e o tráfego não passa pela Cloudflare. Sua origem fica exposta. Use para registros que não são web, como e-mail e verificação de domínio.DNS returns the real address and traffic does not pass through Cloudflare. Your origin is exposed. Use it for non-web records, such as email and domain verification. |
Registros deste desenhoRecords for this layout
| Type | Name | Target | ProxyProxy |
|---|---|---|---|
| CNAME | app | DNS do ALB (xxx.us-east-1.elb.amazonaws.com)the ALB DNS name (xxx.us-east-1.elb.amazonaws.com) | ProxiedProxied |
| CNAME | media | criado pelo R2 ao conectar o domínio personalizadocreated by R2 when you connect the custom domain | ProxiedProxied |
| CNAME | _xxxx (validação do ACM)_xxxx (ACM validation) | valor fornecido pelo ACM, se usar certificado público da AWSvalue given by ACM, if you use an AWS public certificate | DNS onlyDNS only |
3. Certificado SSL e CDN3. SSL certificate and CDN
Há dois trechos de conexão, e cada um tem seu certificado:There are two connection legs, and each has its own certificate:
| TrechoLeg | CertificadoCertificate | Quem cuidaWho handles it |
|---|---|---|
| Visitante → CloudflareVisitor → Cloudflare | Certificado de borda (Universal SSL), gratuito e confiável para navegadoresEdge certificate (Universal SSL), free and browser-trusted | A Cloudflare emite, renova e instala sozinha.Cloudflare issues, renews and installs it automatically. |
| Cloudflare → origem (ALB)Cloudflare → origin (ALB) | Certificado de origem (Cloudflare Origin CA ou de uma CA pública)Origin certificate (Cloudflare Origin CA or from a public CA) | Você instala no ALB (seção 9).You install it on the ALB (section 9). |
Universal SSL (borda)Universal SSL (edge)
É um certificado DV gratuito, que cobre o domínio raiz e os subdomínios de primeiro nível (exemplo.com, app.exemplo.com). Só é emitido depois que o domínio está ativo na Cloudflare. Subdomínios de vários níveis (a.b.exemplo.com) não são cobertos em setup completo; exigem Total TLS ou certificados avançados.It is a free DV certificate covering the apex domain and first-level subdomains (example.com, app.example.com). It is only issued once the domain is active on Cloudflare. Multi-level subdomains (a.b.example.com) are not covered on a full setup; they need Total TLS or advanced certificates.
Modos de criptografiaEncryption modes
| ModoMode | Visitante → CloudflareVisitor → Cloudflare | Cloudflare → origemCloudflare → origin | Valida o certificado da origem?Validates origin certificate? |
|---|---|---|---|
| Off | HTTP | HTTP | NãoNo |
| Flexible | HTTPS | HTTP | NãoNo |
| Full | HTTPS | Acompanha o protocolo do visitanteMatches the visitor’s protocol | Não (aceita autoassinado)No (accepts self-signed) |
| Full (strict) | HTTPS | HTTPS | SimYes |
| Strict (SSL-Only Origin Pull) | HTTP ou HTTPSHTTP or HTTPS | Sempre HTTPSAlways HTTPS | SimYes |
Forçar HTTPS no visitanteForce HTTPS for the visitor
Em SSL/TLS → Edge Certificates, ligue Always Use HTTPS: toda requisição HTTP é redirecionada para HTTPS na borda. A opção só aparece se o modo de criptografia não for Off. A Cloudflare recomenda não fazer esse redirecionamento na origem, porque pode causar loop de redirecionamento.Under SSL/TLS → Edge Certificates, turn on Always Use HTTPS: every HTTP request is redirected to HTTPS at the edge. The option only shows when the encryption mode is not Off. Cloudflare recommends not redirecting at the origin as well, because that can cause redirect loops.
CDN e cacheCDN and cache
Com o proxy ligado, a Cloudflare serve conteúdo de seus pontos de presença e aplica cache, WAF e proteção DDoS. Por padrão só alguns tipos de arquivo são cacheados; para cachear tudo é preciso uma regra de cache (Cache Everything). No R2, o cache só vale quando o bucket é acessado por um domínio personalizado (seção 4); a Cloudflare recomenda o Smart Tiered Cache nesse caso.With the proxy on, Cloudflare serves content from its points of presence and applies cache, WAF and DDoS protection. By default only certain file types are cached; to cache everything you need a cache rule (Cache Everything). On R2, the cache only applies when the bucket is reached through a custom domain (section 4); Cloudflare recommends Smart Tiered Cache in that case.
4. R2: bucket, chaves e acesso4. R2: bucket, keys and access
R2 é o storage de objetos da Cloudflare, com API compatível com S3. Por isso você o acessa com SDKs da AWS (@aws-sdk/client-s3, boto3) apontando o endpoint para a Cloudflare.R2 is Cloudflare’s object storage, with an S3-compatible API. That is why you reach it with AWS SDKs (@aws-sdk/client-s3, boto3) pointing the endpoint at Cloudflare.
Chaves de API (S3)API keys (S3)
- No painel do R2 (Overview), em Account Details, clique em Manage ao lado de API Tokens.In the R2 dashboard (Overview), under Account Details, click Manage next to API Tokens.
- Escolha o tipo: token de conta (vale até ser revogado) ou de usuário (fica inativo se você sair da conta).Choose the type: account token (valid until revoked) or user token (becomes inactive if you leave the account).
- Escolha a permissão e, se quiser, limite a buckets específicos: Object Read & Write (ler, gravar e listar objetos) é o suficiente para gerar URLs pré-assinadas.Choose the permission and, if you like, limit it to specific buckets: Object Read & Write (read, write and list objects) is enough to generate presigned URLs.
- Copie o Access Key ID e o Secret Access Key.Copy the Access Key ID and the Secret Access Key.
O endpoint S3 é https://<ACCOUNT_ID>.r2.cloudflarestorage.com. Buckets com jurisdição (UE, EUA, FedRAMP) só respondem pelo endpoint correspondente, por exemplo <ACCOUNT_ID>.eu.r2.cloudflarestorage.com.The S3 endpoint is https://<ACCOUNT_ID>.r2.cloudflarestorage.com. Jurisdictional buckets (EU, US, FedRAMP) only answer on their matching endpoint, for example <ACCOUNT_ID>.eu.r2.cloudflarestorage.com.
Bucket privado e bucket públicoPrivate and public bucket
Todo bucket nasce privado: nunca é acessível publicamente sem uma ação explícita sua. Há duas formas de abrir leitura pública, e podem ser usadas separadamente:Every bucket starts private: it is never publicly accessible without an explicit action from you. There are two ways to open public reads, and they can be used independently:
| Domínio personalizadoCustom domain | Subdomínio r2.devr2.dev subdomain | |
|---|---|---|
| UsoUse | ProduçãoProduction | Só desenvolvimento; tem limite de requisiçõesDevelopment only; rate-limited |
| Cache / CDNCache / CDN | Sim (Cloudflare Cache)Yes (Cloudflare Cache) | NãoNo |
| WAF, controle de acesso, Bot ManagementWAF, access control, Bot Management | SimYes | NãoNo |
| RequisitoRequirement | Domínio é uma zona na mesma conta do bucketDomain is a zone in the same account as the bucket | NenhumNone |
Para conectar o domínio: bucket → Settings → Custom Domains → Add → informe o domínio (por exemplo media.exemplo.com) → revise o registro DNS → Connect Domain. Aguarde o status passar de Initializing a Active. A listagem do conteúdo na raiz do domínio não existe: só dá para acessar objetos por chave.To connect the domain: bucket → Settings → Custom Domains → Add → enter the domain (for example media.example.com) → review the DNS record → Connect Domain. Wait for the status to go from Initializing to Active. There is no listing of contents at the domain root: objects are only reachable by key.
r2.dev; do contrário o bucket continua público por ele. Para desativar: Public Development URL → Disable e digite disallow.If you protect the bucket with WAF or Cloudflare Access, disable r2.dev too; otherwise the bucket stays public through it. To disable it: Public Development URL → Disable and type disallow.Quando usar cada umWhen to use each
- Privado + URL pré-assinada: arquivos de usuário, documentos, qualquer coisa que exija decidir quem acessa e por quanto tempo.Private + presigned URL: user files, documents, anything where you must decide who gets access and for how long.
- Público + domínio personalizado: imagens e vídeos do site, assets estáticos, tudo que é para qualquer pessoa e que se beneficia de CDN.Public + custom domain: site images and videos, static assets, anything meant for everyone that benefits from a CDN.
5. URLs pré-assinadas: o que são e como funcionam5. Presigned URLs: what they are and how they work
Uma URL pré-assinada dá acesso temporário a um único objeto sem expor suas chaves de API. A assinatura vai embutida na própria URL e é gerada no seu servidor, sem falar com o R2: o cálculo usa as suas chaves e o algoritmo AWS Signature Version 4.A presigned URL grants temporary access to a single object without exposing your API keys. The signature is embedded in the URL itself and is generated on your server, without contacting R2: the calculation uses your keys and the AWS Signature Version 4 algorithm.
O que a assinatura amarraWhat the signature binds
- Recurso: conta, bucket e caminho do objeto.Resource: account, bucket and object path.
- Operação: GET, PUT, HEAD ou DELETE. POST (formulário HTML) não é suportado.Operation: GET, PUT, HEAD or DELETE. POST (HTML form uploads) is not supported.
- Validade: de 1 segundo a 7 dias (604.800 s).Expiry: from 1 second to 7 days (604,800 s).
- Content-Type, se você o informar ao gerar uma URL de PUT: o cliente tem de enviar exatamente esse tipo.Content-Type, if you set it when generating a PUT URL: the client must send exactly that type.
Alterar qualquer um desses parâmetros na URL (ou enviar outro Content-Type) invalida a assinatura e o R2 responde 403 SignatureDoesNotMatch. Quando o R2 recebe a requisição, ele recalcula a assinatura com a mesma chave e compara; se a URL ainda estiver dentro da validade, executa a operação.Changing any of these parameters in the URL (or sending another Content-Type) invalidates the signature and R2 answers 403 SignatureDoesNotMatch. When R2 receives the request, it recomputes the signature with the same key and compares; if the URL is still within its validity, it performs the operation.
<ACCOUNT_ID>.r2.cloudflarestorage.com). Não funcionam em domínio personalizado. Para controlar acesso num domínio personalizado há a validação HMAC do WAF, a partir do plano Pro.Presigned URLs work only on the S3 API domain (<ACCOUNT_ID>.r2.cloudflarestorage.com). They do not work on a custom domain. To control access on a custom domain there is WAF HMAC validation, on the Pro plan or above.Gerando no servidor (Node.js)Generating on the server (Node.js)
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
const r2 = new S3Client({
region: "auto", // required by the SDK, not used by R2
endpoint: process.env.R2_ENDPOINT,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
export function signedDownloadUrl(key) {
const command = new GetObjectCommand({ Bucket: process.env.R2_BUCKET, Key: key });
return getSignedUrl(r2, command, { expiresIn: 3600 });
}
export function signedUploadUrl(key, contentType) {
const command = new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: key,
ContentType: contentType, // the browser must send exactly this Content-Type
});
return getSignedUrl(r2, command, { expiresIn: 600 });
}O mesmo em PythonThe same in Python
import os, boto3
r2 = boto3.client(
service_name="s3",
endpoint_url=os.environ["R2_ENDPOINT"],
aws_access_key_id=os.environ["R2_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["R2_SECRET_ACCESS_KEY"],
region_name="auto", # required by the SDK, not used by R2
)
download_url = r2.generate_presigned_url(
"get_object",
Params={"Bucket": os.environ["R2_BUCKET"], "Key": "image.png"},
ExpiresIn=3600,
)
upload_url = r2.generate_presigned_url(
"put_object",
Params={"Bucket": os.environ["R2_BUCKET"], "Key": "image.png", "ContentType": "image/png"},
ExpiresIn=600,
)A CLI da AWS (aws s3 presign) só gera URLs de GET; para PUT use um SDK.The AWS CLI (aws s3 presign) only generates GET URLs; for PUT use an SDK.
Fluxo de upload pelo navegadorBrowser upload flow
- O navegador pede ao seu backend uma URL de upload, informando o nome e o tipo do arquivo. O backend decide a chave do objeto e gera a URL.The browser asks your backend for an upload URL, sending the file name and type. The backend decides the object key and generates the URL.
- O navegador envia o arquivo direto ao R2 com
PUT. O arquivo não passa pelo seu servidor.The browser sends the file straight to R2 withPUT. The file never goes through your server. - O backend guarda a chave do objeto (não a URL) no banco. Quando alguém precisar ver o arquivo, gera uma URL de download nova.The backend stores the object key (not the URL) in the database. When someone needs to see the file, it generates a fresh download URL.
const { url } = await (await fetch("/api/uploads", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: file.name, type: file.type }),
})).json();
await fetch(url, {
method: "PUT",
headers: { "Content-Type": file.type }, // same value used to sign the URL
body: file,
});CORS no bucketCORS on the bucket
Como o navegador faz esse PUT para outra origem, o R2 precisa de uma política de CORS. Um PUT é sempre precedido de uma requisição preflight (OPTIONS); o navegador só envia o arquivo se o bucket autorizar a origem, o método e o cabeçalho. Em bucket → Settings → CORS Policy → Add CORS policy (aba JSON):Because the browser makes this PUT to another origin, R2 needs a CORS policy. A PUT is always preceded by a preflight request (OPTIONS); the browser only sends the file if the bucket allows the origin, the method and the header. Under bucket → Settings → CORS Policy → Add CORS policy (JSON tab):
[
{
"AllowedOrigins": ["https://app.example.com"],
"AllowedMethods": ["PUT", "GET"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}
]Ao mudar a política de um domínio personalizado, limpe o cache do hostname (Cache Purge), porque respostas cacheadas podem não ter os novos cabeçalhos.When you change the policy of a custom domain, purge the hostname’s cache (Cache Purge), because cached responses may not carry the new headers.
6. Como o navegador renderiza foto e vídeo6. How the browser renders photos and video
Para o navegador, uma URL pré-assinada é uma URL comum. Ele não sabe de assinatura: faz um GET e usa o que vier na resposta.To the browser, a presigned URL is an ordinary URL. It knows nothing about signatures: it makes a GET and uses whatever comes back.
Foto: <img>Photo: <img>
<img src="https://my-bucket.<ACCOUNT_ID>.r2.cloudflarestorage.com/photo.jpg?X-Amz-Algorithm=…&X-Amz-Signature=…" alt="Description">- O navegador encontra o
<img>e dispara umGETpara a URL, com a assinatura na query string.The browser finds the<img>and fires aGETto the URL, with the signature in the query string. - O R2 confere a assinatura e a validade e responde
200com o corpo do arquivo e o cabeçalhoContent-Type(por exemploimage/jpeg).R2 checks the signature and the expiry and answers200with the file body and theContent-Typeheader (for exampleimage/jpeg). - O navegador decodifica os bytes conforme o tipo e desenha a imagem. Se a URL expirou ou foi alterada, a resposta é 403 e aparece a imagem quebrada.The browser decodes the bytes according to the type and paints the image. If the URL expired or was altered, the answer is 403 and a broken image appears.
Mostrar a imagem não exige CORS: elementos <img> e <video> carregam recursos de outra origem normalmente. O CORS passa a ser necessário quando o código lê os dados, por exemplo com fetch(), ou desenha a imagem ou um quadro de vídeo num <canvas>; nesse caso use o atributo crossorigin e configure o CORS do bucket.Showing the image does not require CORS: <img> and <video> elements load cross-origin resources normally. CORS becomes necessary when code reads the data, for example with fetch(), or draws the image or a video frame to a <canvas>; in that case use the crossorigin attribute and configure the bucket’s CORS.
Vídeo: <video>Video: <video>
<video controls preload="metadata" playsinline poster="poster.jpg">
<source src="https://…signed-url…/clip.mp4" type="video/mp4">
</video>O vídeo é diferente da foto porque o navegador não baixa tudo de uma vez. Ele usa requisições por faixa de bytes (HTTP Range):Video differs from a photo because the browser does not download everything at once. It uses byte-range requests (HTTP Range):
- Com
preload="metadata"o navegador busca só o começo do arquivo, o bastante para saber a duração e as dimensões, e mostra oposter(ou o primeiro quadro).Withpreload="metadata"the browser fetches only the start of the file, enough to learn duration and dimensions, and shows theposter(or the first frame). - O servidor precisa anunciar
Accept-Ranges: bytes. O navegador então pede trechos comRange: bytes=0-1023e recebe206 Partial ContentcomContent-Range.The server must advertiseAccept-Ranges: bytes. The browser then asks for chunks withRange: bytes=0-1023and receives206 Partial ContentwithContent-Range. - Ao tocar o vídeo ou arrastar a barra de progresso, o navegador pede o trecho do ponto escolhido, sem baixar o que vem antes. Sem suporte a Range, o servidor manda o arquivo inteiro com
200e não dá para pular com eficiência.When you play or drag the progress bar, the browser asks for the chunk at the chosen point, without downloading what comes before. Without Range support the server sends the whole file with200and seeking is not efficient.
O R2 aceita o cabeçalho Range em GetObject, então vídeo em bucket R2 pode ser buscado por trechos.R2 supports the Range header on GetObject, so video in an R2 bucket can be fetched in chunks.
Content-Type, o detalhe que mais importaContent-Type, the detail that matters most
- O navegador decide como decodificar pelo
Content-Typeda resposta. Se o tipo estiver errado ou faltar, a mídia pode não aparecer; vídeos podem exibir um quadro cinza com um X.The browser decides how to decode by the response’sContent-Type. If the type is wrong or missing, the media may not show; videos can display a grey box with an X. - No R2 o tipo vem dos metadados gravados no upload (
PutObjectaceitaContent-Type,Cache-ControleContent-Disposition). Por isso assine o PUT com oContentTypecorreto, como no exemplo da seção 5.On R2 the type comes from the metadata stored at upload (PutObjectacceptsContent-Type,Cache-ControlandContent-Disposition). That is why you sign the PUT with the correctContentType, as in the example in section 5. - No
<video>, o atributotypedo<source>(video/mp4,video/webm) deixa o navegador pular formatos que não toca, sem baixá-los.On<video>, thetypeattribute of<source>(video/mp4,video/webm) lets the browser skip formats it cannot play, without downloading them. Content-Disposition: attachmentpede ao navegador que baixe o arquivo em vez de exibi-lo;inline(padrão) o exibe. Para mídia exibida em<img>/<video>, eviteattachmente teste nos navegadores que você atende: a documentação do MDN descreve o cabeçalho para páginas e downloads, não para esses elementos.Content-Disposition: attachmentasks the browser to download the file instead of displaying it;inline(the default) displays it. For media shown in<img>/<video>, avoidattachmentand test in the browsers you support: MDN describes the header for pages and downloads, not for these elements.
Presignado ou público: o que muda na renderizaçãoPresigned or public: what changes in rendering
| URL pré-assinada (privado)Presigned URL (private) | Domínio personalizado (público)Custom domain (public) | |
|---|---|---|
| EndereçoAddress | …r2.cloudflarestorage.com/chave?X-Amz-…, muda a cada geração…r2.cloudflarestorage.com/key?X-Amz-…, changes on every generation | https://media.exemplo.com/chave, estávelhttps://media.example.com/key, stable |
| Expira?Expires? | Sim, até 7 diasYes, up to 7 days | NãoNo |
| CDN da CloudflareCloudflare CDN | Não (domínio da API S3)No (S3 API domain) | SimYes |
| Quem pode verWho can see | Quem tiver a URL, até expirarWhoever has the URL, until it expires | Qualquer pessoaAnyone |
7. Variáveis, valores e chaves7. Variables, values and keys
O que cada parte precisa e onde obter. Segredos vão para o AWS Secrets Manager ou o Parameter Store (veja as notas de AWS), nunca para o repositório.What each part needs and where to get it. Secrets go to AWS Secrets Manager or Parameter Store (see the AWS notes), never into the repository.
| Variável / valorVariable / value | O que é e onde obterWhat it is and where to get it | Segredo?Secret? |
|---|---|---|
R2_ACCOUNT_ID | ID da conta Cloudflare, mostrado no painel do R2. Compõe o endpoint.Cloudflare account ID, shown in the R2 dashboard. Part of the endpoint. | NãoNo |
R2_ENDPOINT | https://<ACCOUNT_ID>.r2.cloudflarestorage.com (com jurisdição: .eu., .us., .fedramp.).https://<ACCOUNT_ID>.r2.cloudflarestorage.com (with a jurisdiction: .eu., .us., .fedramp.). | NãoNo |
R2_ACCESS_KEY_ID | Gerado em R2 → API Tokens (seção 4).Generated under R2 → API Tokens (section 4). | SimYes |
R2_SECRET_ACCESS_KEY | Gerado junto com o Access Key ID; exibido uma única vez.Generated together with the Access Key ID; shown only once. | SimYes |
R2_BUCKET | Nome do bucket criado no R2.Name of the bucket created in R2. | NãoNo |
R2_PRESIGN_EXPIRES_SECONDS | Validade das URLs. De 1 a 604.800 (7 dias). Use valores curtos.URL validity. From 1 to 604,800 (7 days). Use short values. | NãoNo |
R2_PUBLIC_BASE_URL | Domínio personalizado do bucket público (https://media.exemplo.com).Custom domain of the public bucket (https://media.example.com). | NãoNo |
Região do SDK = autoSDK region = auto | Obrigatória pelo SDK, mas o R2 não a usa.Required by the SDK, but R2 does not use it. | NãoNo |
origin.pem | Certificado de origem (Cloudflare Origin CA), parte pública (seção 9).Origin certificate (Cloudflare Origin CA), public part (section 9). | NãoNo |
origin.key | Chave privada do certificado de origem; só aparece na criação.Private key of the origin certificate; only shown at creation. | SimYes |
CERTIFICATE_ARN | ARN devolvido pelo aws acm import-certificate, usado no listener HTTPS.ARN returned by aws acm import-certificate, used on the HTTPS listener. | NãoNo |
ALB_ARN, TARGET_GROUP_ARN | ARNs do load balancer e do target group na AWS.ARNs of the load balancer and target group on AWS. | NãoNo |
Exemplo de arquivo de ambiente (backend)Example environment file (backend)
# --- Cloudflare R2 (backend) ---
R2_ACCOUNT_ID=<ACCOUNT_ID>
R2_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com
R2_ACCESS_KEY_ID=<ACCESS_KEY_ID>
R2_SECRET_ACCESS_KEY=<SECRET_ACCESS_KEY>
R2_BUCKET=<BUCKET_NAME>
R2_PRESIGN_EXPIRES_SECONDS=600
# only if the bucket has a public custom domain
R2_PUBLIC_BASE_URL=https://media.example.com.env no Git. No ECS, injete os segredos como secrets da task definition, vindos do Secrets Manager ou do Parameter Store.Do not commit the .env to Git. On ECS, inject the secrets as task definition secrets, coming from Secrets Manager or Parameter Store.O token de API do R2 pode ser limitado a buckets específicos (permissões Object). Crie um token por ambiente (develop e produção), cada um com acesso só ao bucket daquele ambiente.The R2 API token can be limited to specific buckets (Object permissions). Create one token per environment (develop and production), each with access only to that environment’s bucket.
8. O que cada recurso precisa para funcionar8. What each feature needs to work
| RecursoFeature | Precisa deNeeds |
|---|---|
| Domínio funcionandoWorking domain | Registro/NS na Cloudflare, zona ativa, registro DNS com proxy.Registration/NS on Cloudflare, active zone, a proxied DNS record. |
| HTTPS para o visitanteHTTPS for the visitor | Zona ativa (Universal SSL) e Always Use HTTPS.Active zone (Universal SSL) and Always Use HTTPS. |
| HTTPS até a AWSHTTPS to AWS | Modo Full (strict), certificado no ALB (seção 9), listener 443, SG liberando 443.Full (strict) mode, a certificate on the ALB (section 9), a 443 listener, a security group allowing 443. |
| Upload por URL pré-assinadaPresigned upload | Chaves S3 do R2, endpoint, bucket, backend que assina o PUT com ContentType, CORS com PUT e Content-Type.R2 S3 keys, endpoint, bucket, a backend that signs the PUT with ContentType, CORS with PUT and Content-Type. |
| Download por URL pré-assinadaPresigned download | Chaves S3, endpoint, bucket, backend que assina o GET com validade suficiente.S3 keys, endpoint, bucket, a backend that signs the GET with enough validity. |
| Mídia pública com CDNPublic media with CDN | Domínio personalizado no bucket (zona na mesma conta) e regra de cache, se quiser cachear todos os tipos.Custom domain on the bucket (zone in the same account) and a cache rule, if you want every type cached. |
9. Certificado no ALB: HTTPS até a AWS9. Certificate on the ALB: HTTPS all the way to AWS
Com o modo Full (strict), a Cloudflare conecta ao ALB por HTTPS e valida o certificado. O ALB precisa, então, de um certificado válido para o hostname (por exemplo app.exemplo.com) num listener HTTPS. Há dois caminhos; este usa o certificado de origem gratuito da Cloudflare.With Full (strict) mode, Cloudflare connects to the ALB over HTTPS and validates the certificate. The ALB therefore needs a valid certificate for the hostname (for example app.example.com) on an HTTPS listener. There are two paths; this one uses Cloudflare’s free origin certificate.
Passo 1: criar o certificado de origem na CloudflareStep 1: create the origin certificate on Cloudflare
- Painel → seu domínio → SSL/TLS → Origin Server → Origin Certificates → Create Certificate.Dashboard → your domain → SSL/TLS → Origin Server → Origin Certificates → Create Certificate.
- Deixe a Cloudflare gerar a chave privada e o CSR, e escolha o tipo da chave (RSA ou ECC).Let Cloudflare generate the private key and CSR, and choose the key type (RSA or ECC).
- Liste os hostnames cobertos (a raiz e o curinga de primeiro nível vêm por padrão) e o prazo de validade.List the covered hostnames (the apex and the first-level wildcard come by default) and the validity period.
- Escolha o formato PEM e salve o certificado como
origin.peme a chave privada comoorigin.key.Choose the PEM format and save the certificate asorigin.pemand the private key asorigin.key.
Passo 2: importar para o ACMStep 2: import into ACM
O ACM exige certificado e chave em PEM, e a chave sem senha (iniciando em -----BEGIN PRIVATE KEY-----, com no máximo 5 KB). O ALB aceita todos os algoritmos do ACM (RSA 1024 a 4096 e ECDSA 256, 384 e 521). O certificado precisa estar dentro da validade no momento da importação. Importe na mesma região do load balancer.ACM requires the certificate and key in PEM, and the key without a passphrase (starting with -----BEGIN PRIVATE KEY-----, up to 5 KB). The ALB accepts every ACM algorithm (RSA 1024 to 4096 and ECDSA 256, 384 and 521). The certificate must be inside its validity period at import time. Import in the same Region as the load balancer.
aws acm import-certificate \
--certificate fileb://origin.pem \
--private-key fileb://origin.key
# prints the certificate ARN: save it as CERTIFICATE_ARNA cadeia (--certificate-chain) é opcional. Se for usar, ela deve estar em PEM, em ordem, sem repetir o seu certificado. Também dá para importar pelo console: ACM → Import a certificate e colar o corpo, a chave privada e, se houver, a cadeia.The chain (--certificate-chain) is optional. If you use it, it must be PEM, in order, without repeating your own certificate. You can also import through the console: ACM → Import a certificate and paste the body, the private key and, if any, the chain.
Passo 3: listener HTTPS no ALBStep 3: HTTPS listener on the ALB
aws elbv2 create-listener \
--load-balancer-arn <ALB_ARN> \
--protocol HTTPS \
--port 443 \
--default-actions Type=forward,TargetGroupArn=<TARGET_GROUP_ARN> \
--ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
--certificates CertificateArn=<CERTIFICATE_ARN>No console: EC2 → Load Balancers → seu ALB → Listeners and rules → Add listener, protocolo HTTPS, porta 443, ação Forward to target groups, política de segurança e, em Default SSL/TLS certificate, o certificado do ACM importado. O ALB usa esse certificado para encerrar a conexão do cliente (a Cloudflare, neste caso) e então repassa a requisição aos alvos. O ALB não suporta chaves ED25519.In the console: EC2 → Load Balancers → your ALB → Listeners and rules → Add listener, protocol HTTPS, port 443, action Forward to target groups, a security policy and, under Default SSL/TLS certificate, the imported ACM certificate. The ALB uses that certificate to terminate the client connection (Cloudflare, in this case) and then forwards the request to the targets. The ALB does not support ED25519 keys.
Passo 4: nada em HTTP puroStep 4: nothing over plain HTTP
Para garantir que nada chegue ao ALB sem criptografia, libere no security group do ALB só a porta 443 e, se mantiver a porta 80, faça dela um redirecionamento permanente para HTTPS:To make sure nothing reaches the ALB unencrypted, allow only port 443 in the ALB’s security group and, if you keep port 80, make it a permanent redirect to HTTPS:
aws elbv2 create-listener \
--load-balancer-arn <ALB_ARN> \
--protocol HTTP \
--port 80 \
--default-actions '[{
"Type": "redirect",
"RedirectConfig": {
"Protocol": "HTTPS",
"Port": "443",
"Host": "#{host}",
"Path": "/#{path}",
"Query": "#{query}",
"StatusCode": "HTTP_301"
}
}]'Como o modo Full (strict) já faz a Cloudflare falar HTTPS com a origem, esse redirecionamento no ALB não cria loop; ele só protege contra acessos que ignorem a Cloudflare.Because Full (strict) mode already makes Cloudflare speak HTTPS to the origin, this redirect on the ALB does not create a loop; it only protects against access that bypasses Cloudflare.
Passo 5: ligar a Cloudflare ao ALBStep 5: connect Cloudflare to the ALB
- Em DNS → Records, crie um CNAME
appapontando para o nome DNS do ALB, com proxy ligado.Under DNS → Records, create a CNAMEapppointing to the ALB DNS name, with the proxy on. - Em SSL/TLS → Overview, escolha Full (strict).Under SSL/TLS → Overview, choose Full (strict).
- Em Edge Certificates, ligue Always Use HTTPS.Under Edge Certificates, turn on Always Use HTTPS.
- Teste:
curl -I https://app.exemplo.comdeve responder pela Cloudflare, ehttp://app.exemplo.comdeve redirecionar para HTTPS.Test:curl -I https://app.example.comshould answer through Cloudflare, andhttp://app.example.comshould redirect to HTTPS.
Resultado: visitante → Cloudflare em HTTPS (certificado de borda) e Cloudflare → ALB em HTTPS (certificado de origem validado). A Cloudflare conecta ao seu domínio nas portas HTTPS que ela proxia (a 443 está entre elas).Result: visitor → Cloudflare over HTTPS (edge certificate) and Cloudflare → ALB over HTTPS (validated origin certificate). Cloudflare proxies the HTTPS ports it supports (443 is among them).
Alternativa: certificado público do ACMAlternative: ACM public certificate
O Full (strict) também aceita certificados de CA pública. Um certificado público do ACM é gratuito e se renova sozinho, mas a validação é por DNS: o ACM fornece um CNAME de validação que você cria na Cloudflare como DNS only. Esse caminho evita ter de controlar a expiração do certificado de origem (veja a seção ACM das notas de AWS).Full (strict) also accepts certificates from a public CA. An ACM public certificate is free and renews itself, but validation is by DNS: ACM gives you a validation CNAME that you create on Cloudflare as DNS only. This path avoids having to track the origin certificate’s expiry (see the ACM section of the AWS notes).
RenovaçãoRenewal
- Certificado de origem da Cloudflare: sem aviso de expiração. Renove-o criando outro e reimportando-o no ACM; a reimportação não pode remover extensões KeyUsage/ExtendedKeyUsage que o certificado anterior tinha (com exceções descritas nas docs do ACM).Cloudflare origin certificate: no expiry notice. Renew it by creating another and reimporting it into ACM; a reimport cannot remove KeyUsage/ExtendedKeyUsage extensions that the previous certificate had (with exceptions described in the ACM docs).
- Certificado de borda (Universal SSL): a Cloudflare cuida da emissão, renovação e instalação.Edge certificate (Universal SSL): Cloudflare handles issuance, renewal and installation.
10. Problemas comuns10. Common problems
- 403 SignatureDoesNotMatch no upload: o
Content-Typedo PUT é diferente do usado para assinar, ou a URL foi alterada.403 SignatureDoesNotMatch on upload: the PUT’sContent-Typediffers from the one used to sign, or the URL was altered. - Erro de CORS no upload: falta a política no bucket, ou ela não inclui a origem, o método PUT ou o cabeçalho
Content-Type.CORS error on upload: the bucket policy is missing, or it does not include the origin, the PUT method or theContent-Typeheader. - URL pré-assinada não funciona no meu domínio: ela só funciona no domínio da API S3, não em domínio personalizado.Presigned URL does not work on my domain: it only works on the S3 API domain, not on a custom domain.
- Vídeo não pula ou trava: a URL expirou durante a reprodução, ou o servidor não responde a
Rangecom206.Video does not seek or stalls: the URL expired during playback, or the server does not answerRangewith206. - Mídia não aparece:
Content-Typeerrado ou ausente no objeto.Media does not show: wrong or missingContent-Typeon the object. - Erro 524: a origem demorou mais que o Proxy Read Timeout da Cloudflare para responder.Error 524: the origin took longer than Cloudflare’s Proxy Read Timeout to answer.
- Domínio parou de resolver: a verificação do e-mail do registrante expirou e a ICANN pôs o domínio em hold.Domain stopped resolving: registrant email verification expired and ICANN put the domain on hold.
- Domínio personalizado do bucket preso em Initializing: use Retry connection; confira se a zona está na mesma conta do bucket.Bucket custom domain stuck on Initializing: use Retry connection; check that the zone is in the same account as the bucket.
- Cloudflare Docs · Register a domain (Registrar)
- Cloudflare Docs · Add a site
- Cloudflare Docs · Proxy status
- Cloudflare Docs · Universal SSL
- Cloudflare Docs · SSL/TLS encryption modes
- Cloudflare Docs · Origin CA certificates
- Cloudflare Docs · Always Use HTTPS
- Cloudflare Docs · Network ports
- Cloudflare Docs · R2 API tokens
- Cloudflare Docs · R2 presigned URLs
- Cloudflare Docs · R2 S3 API compatibility
- Cloudflare Docs · R2 public buckets
- Cloudflare Docs · R2 CORS
- AWS Docs · Prerequisites for importing ACM certificates
- AWS Docs · Certificate and key format for importing
- AWS Docs · Import a certificate
- AWS Docs · Create an HTTPS listener for your Application Load Balancer
- AWS Docs · Action types for listener rules (redirect)
- MDN · Cross-Origin Resource Sharing (CORS)
- MDN · HTTP range requests
- MDN · The <video> element
- MDN · Content-Disposition