TL;DR

1
2
PUB=$(openssl rsa -in dkim.key -pubout -outform DER 2>/dev/null | base64 | tr -d '\n')
echo "v=DKIM1; k=rsa; p=$PUB" | fold -w 255 | sed 's/.*/"&"/' | paste -sd' ' -

That prints the value ready to paste into the DNS provider, two quoted strings separated by a space:

1
"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvs7EROOYh35C/1s7M//pIaRvu1XWXzdexgBR2dO7eq1oDw+X08BNJUVUEte3z7os5CNeP7YfZrAqyDJvA59wMK1qul6oHyy7z5P1aB4zNczvO7SZ20HogWjHjzYJ0qwmSUUtjlKvECQzoTCbS/F0gB9rdUZN02MJopDs8YWcohy8oqNLnrflHdxWm6jAb3sSa" "EQReu6iRKKPiXTGUISSetY+iXLo2j/2c5g5ppiIYkUTq58asamMNiCzB7BeF4WNkjz2P+9IJmmtkkJ0M/U4oThUMvBBtQ/XUV4i2N4PGB359G5d/PIMiCj4go2axA4p8ul7Qafbtv0fyPwOIGSojwIDAQAB"

Both go into the same TXT record. Whoever verifies DKIM concatenates them with no spaces. If Google Workspace or Microsoft 365 handed you the text and you do not have the private key, it is the same without the openssl: echo "<value>" | fold -w 255 | sed 's/.*/"&"/' | paste -sd' ' -.

The problem

A TXT record is made of one or more character-strings, and each one is capped at 255 bytes (RFC 1035). The whole record can be much longer; what it cannot have is a single 410-character string.

RSA keyLength of p=With v=DKIM1; k=rsa; p=Fits in one string?
1024 bits216234Yes
2048 bits392410No: two are needed
4096 bits736754No: three

Google Workspace and Microsoft 365 generate 2048-bit keys by default, and rightly so: 1024 has been discouraged since RFC 8301. So almost any new DKIM setup today runs into this, and each provider reacts differently:

ProviderWhat happens with 410 characters in a row
Route 53Rejects it with CharacterStringTooLong, in the console and the CLI
Google Cloud DNSRejects the value; you have to type the quoted strings yourself
CloudflareAccepts it and splits it into 255-character chunks on its own
BIND and most self-hosted serversThe zone file does not load until you use several strings

RFC 6376 (§3.6.2.2) requires verifiers to concatenate the strings before using them, so splitting the key is not a trick: it is the intended format.

Per provider

BIND / zone file

1
2
selector._domainkey  300  IN  TXT  ( "v=DKIM1; k=rsa; p=MIIBIjANBgkq...first part"
                                     "second part...IDAQAB" )

Route 53

In the console, paste both quoted strings into Value separated by a space or a line break. From the CLI the value is a single string with escaped quotes:

1
2
3
4
5
aws route53 change-resource-record-sets --hosted-zone-id Z0123456789EXAMPLE --change-batch '{
  "Changes": [{"Action": "UPSERT", "ResourceRecordSet": {
    "Name": "selector._domainkey.example.com", "Type": "TXT", "TTL": 300,
    "ResourceRecords": [{"Value": "\"v=DKIM1; k=rsa; p=...first part\" \"second part...\""}]
  }}]}'

Google Cloud DNS

In the console, same format in the data field. With gcloud:

1
2
3
gcloud dns record-sets create selector._domainkey.example.com. --zone=example-zone \
  --type=TXT --ttl=300 \
  --rrdatas='"v=DKIM1; k=rsa; p=...first part" "second part..."'

With Terraform (google_dns_record_set), a single rrdatas element with escaped quotes:

1
rrdatas = ["\"v=DKIM1; k=rsa; p=...first part\" \"second part...\""]

Cloudflare

Paste the full value into Content; the dashboard splits it on save. When you query it with dig you will see two strings even though you entered one: that is normal.

Verifying

1
dig +short TXT selector._domainkey.example.com

Expected: "v=DKIM1; k=rsa; p=MIIB..." "...IDAQAB", two strings, one record. Then check that the key decodes to 2048 bits, which confirms no character was lost at the cut:

1
2
3
4
dig +short TXT selector._domainkey.example.com \
  | tr -d '" \n' | sed -E 's/.*p=([A-Za-z0-9+/=]+).*/\1/' \
  | base64 -d | openssl pkey -pubin -inform DER -noout -text | head -1
# Public-Key: (2048 bit)

If base64 complains, a space slipped inside the key or characters are missing. Finish with a test email and Authentication-Results: ... dkim=pass in the headers; for a closer look, dmarcian’s DKIM Inspector.

Traps

  • The cut can go anywhere, even mid-base64, but never put spaces inside the key. The space between the two strings separates strings; it is not data.
  • Do not double the quotes. If the panel adds them for you and you paste "...", dig returns "\"v=DKIM1...\"" and the verifier fails.
  • One record, not two. Two separate TXT records with the same name are two independent values, and neither is a valid key.
  • 2048 is the sweet spot. 4096 means three strings and a DNS answer close to the UDP limit; it works, but gains nothing for DKIM.
  • Rotation: publish the new key under a new selector, switch the signer, and delete the old selector after the TTL. Never edit the selector in use.
  • SPF has the same limit, but an SPF that does not fit in 255 is usually a sign of too many include:; there the fix is shortening it, not splitting it.

References: Google Workspace — troubleshoot DKIM issues, AWS re:Post — CharacterStringTooLong, and the Mailhardener DNS record splitter if you would rather not use fold.