没有不值得去解决的问题,也没有不值得去学习的技术!

Domain Security Score Fix: From 71 to 83, the Real Problem Was SPF

[Figure 7: After fixing SPF, Gmail actual verification shows SPF, DKIM, DMARC all PASS]

作者:

在

Recently, I used TCPLab’s “Comprehensive Domain Check” to inspect my main domain:

www.shuijingwanwq.com

Initially, I just wanted to see if there were any obvious issues with the domain, DNS, email authentication, and HTTPS/TLS configurations.

The first test result was:

71 points, Grade B.

I originally thought this would be a routine website configuration check, but I unexpectedly discovered a rather interesting issue later on:

TCPLab did not detect DKIM, but DKIM was actually working fine all along; instead, it was the SPF configuration, which appeared to already exist, that encountered PERMERROR during actual email delivery.

Ultimately, after fixing the issues that genuinely needed to be addressed, the score increased from 71 to 83.

As for the remaining items like DNSSEC, CAA, and CSP, I did not continue processing all of them just to pursue a higher score. This is not because these configurations have no value, but rather because some are further security hardening measures with low immediate necessity. Increasing configuration complexity to gain a few more points, or even introducing new risks of failure, did not seem worth it to me.

Additionally, this experience once again illustrates a point:

The recommendations provided by testing tools are worth referencing, but they should not be followed mechanically.

Some issues genuinely need fixing; others are just optional security enhancements; and still others might simply be cases where the testing tool itself failed to recognize the actual configuration.

Below is a complete record of this process.


1. First Test: 71 Points

The initial TCPLab test result was:

71 points, Grade B.

[Figure 1: First TCPLab test, comprehensive domain score of 71]
[Figure 1: First TCPLab test, comprehensive domain score of 71]

The report primarily flagged these issues:

  • The domain is about to expire;
  • HTTP does not enforce a redirect to HTTPS;
  • DMARC is missing;
  • DKIM not detected;
  • DNSSEC is not enabled;
  • CAA is missing;
  • Web security response headers are incomplete.

I did not treat all these warnings as “issues that must be fixed immediately.”

My approach to handling them was divided into two categories:

The first category includes items that genuinely affect current usage or security fundamentals, such as domain renewal, HTTP → HTTPS, and email authentication.

The second category includes enhancements that have security value but are lower priority right now, such as DNSSEC, CAA, and certain web security response headers.

I handled things this way mainly to focus my energy on truly necessary issues, rather than adding a bunch of configurations with limited immediate benefit just to improve the test score.

As for whether the test results themselves were entirely accurate, that was something I only discovered later while troubleshooting SPF and DKIM.


2. Domain Renewal: This Check Was Accurate

At the time of the test, the domain really was only a few days away from expiring.

I had actually received a domain renewal reminder email a while back, but because I had been busy lately, I hadn’t set aside time to handle it.

So this TCPLab warning was not a false positive.

After seeing the test result, I made renewal my top priority and subsequently completed the domain renewal.

Compared to security configurations like DNSSEC, CAA, and CSP, domain renewal is actually the most fundamental item.

After all:

No matter how perfectly DNSSEC, CAA, and CSP are configured, if you forget to renew the domain, none of it means anything.

So, at the very least, this test reminded me to promptly finish a task I already knew about but hadn’t yet handled.


3. HTTP → HTTPS: Originally Working for a Long Time, Broken at Some Unknown Point

Next, I checked:

Plaintext
curl -I http://www.shuijingwanwq.com/

The result returned:

Plaintext
HTTP/1.1 200 OK

Meaning:

Plaintext
http://www.shuijingwanwq.com/

was directly returning the page instead of redirecting to HTTPS.

This actually surprised me a bit.

Because my blog has been running for over a decade, the HTTP → HTTPS 301 redirect was configured correctly a long time ago.

So this was not a long-standing fundamental mistake.

It is more likely that during a later modification of the Nginx configuration, I accidentally altered the original HTTP 301 redirect logic without noticing it.

HTTPS itself was working fine, and there were no issues with TLS or HSTS.

The actual anomaly was:

HTTP requests were no longer redirecting, but directly returning a 200.

I did not go back and modify Nginx this time.

Because there is already a CDN / edge acceleration service in front of the website, using the CDN layer to directly handle the HTTP → HTTPS redirect is simpler and more straightforward.

My main site uses Tencent Cloud EdgeOne, so I navigated to:

Site Acceleration → HTTPS → Force HTTPS

I enabled global forced HTTPS, and selected:

Plaintext
301

as the redirect method.

[Figure 2: Enabling Force HTTPS in Tencent Cloud EdgeOne and using 301 redirect]
[Figure 2: Enabling Force HTTPS in Tencent Cloud EdgeOne and using 301 redirect]

After saving, I tested again:

Plaintext
curl -I http://www.shuijingwanwq.com/

The result became:

Plaintext
HTTP/1.1 301 Moved Permanently
Location: https://www.shuijingwanwq.com/

I also took the opportunity to check a few other subdomains:

Plaintext
curl -I http://go-dev.shuijingwanwq.com/
curl -I http://assets-go-dev.shuijingwanwq.com/
curl -I http://ja-go-dev.shuijingwanwq.com/

They were all able to redirect to HTTPS normally.

Compared to tracing back exactly which Nginx change affected the original configuration, handling it uniformly at the CDN layer is much simpler.

This concludes this fix.


4. Adding DMARC

Next, I handled email authentication.

My shuijingwanwq.com mailbox uses Tencent Enterprise Email.

The MX records are:

Plaintext
5 mxbiz1.qq.com.
10 mxbiz2.qq.com.

First, I checked DMARC:

Plaintext
dig TXT _dmarc.shuijingwanwq.com +short

There was no output.

This indicated that DMARC was indeed not configured at the time.

So, I added a new record in Cloudflare DNS:

Plaintext
Type: TXT
Name: _dmarc
Content: v=DMARC1; p=none
[Figure 3: Adding a DMARC record in Cloudflare DNS]
[Figure 3: Adding a DMARC record in Cloudflare DNS]

After saving, I verified:

Plaintext
dig TXT _dmarc.shuijingwanwq.com +short

It was now able to return:

Plaintext
"v=DMARC1; p=none"

I did not directly use:

Plaintext
p=quarantine

or:

Plaintext
p=reject

but instead used:

Plaintext
p=none

The reason is that I wanted to first confirm whether my current legitimate emails’ SPF / DKIM were working normally.

There is not much need to use a strict policy right away when initially configuring DMARC.

Later troubleshooting also proved that this caution was worthwhile, because my SPF did indeed have a problem at the time.


5. TCPLab Did Not Detect DKIM, So I Decided to Verify with a Real Email

TCPLab showed:

DKIM not detected.

My first thought was:

Tencent Enterprise Email might already be using DKIM normally, but TCPLab just didn’t know the specific selector, so it couldn’t find the corresponding record.

Because DKIM usually does not query:

Plaintext
_domainkey.example.com

but rather:

Plaintext
selector._domainkey.example.com

If the selector is unknown, a scanner might not necessarily be able to discover the corresponding DKIM record.

Rather than guessing the selector, I felt a more direct approach was:

To actually send an email.

So I used:

Plaintext
admin@shuijingwanwq.com

to send a test email from Tencent Enterprise Email to Gmail.

Then, in Gmail, I opened:

More → Show original

The result immediately revealed the real problem.


6. First Real Email Verification: The Real Problem Was SPF

The authentication results provided by Gmail were:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS
[Figure 4: First Gmail actual email verification, SPF is PERMERROR, while DKIM and DMARC are both PASS]
[Figure 4: First Gmail actual email verification, SPF is PERMERROR, while DKIM and DMARC are both PASS]

This result is very interesting.

I was originally focused on checking the DKIM issue flagged by TCPLab.

But Gmail, which was actually responsible for receiving this email, told me:

DKIM is fine, SPF is the one with the problem.

This is one of the most noteworthy parts of this troubleshooting process.

Online testing tools can help find clues, but real email delivery provides more direct verification results.


7. DKIM Was Actually Working Fine All Along

Continuing to look through the original Gmail email, I could find:

Plaintext
DKIM-Signature:

Which explicitly contained:

Plaintext
d=shuijingwanwq.com;
s=xacy2606;
[Figure 5: DKIM-Signature in the original Gmail email, confirming the selector is xacy2606]
[Figure 5: DKIM-Signature in the original Gmail email, confirming the selector is xacy2606]

This means the email used:

Plaintext
DKIM domain: shuijingwanwq.com
selector: xacy2606

Going back to Cloudflare DNS, I could indeed see:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

and its corresponding DKIM TXT record.

So it can be confirmed that:

Tencent Enterprise Email’s DKIM has been working properly all along.

TCPLab’s “DKIM not detected” simply meant it did not recognize the currently used selector:

Plaintext
xacy2606

Therefore, there was no need to continue modifying this item just to turn the test result green.


8. The Real Problem: A Mistake in the SPF Record

In the original Gmail email, SPF returned:

Plaintext
spf=permerror

And explicitly noted:

Plaintext
mail.qq.com not found

At this point, I went back and checked my original SPF:

Plaintext
v=spf1 include:mail.qq.com ~all

The problem was quite clear.

I had written:

Plaintext
include:mail.qq.com

Combined with the error returned by Gmail and the current SPF configuration for Tencent Enterprise Email, I finally confirmed that my scenario should use:

Plaintext
include:spf.mail.qq.com

So I changed the SPF to:

Plaintext
v=spf1 include:spf.mail.qq.com ~all

A special note here:

This is only the result that was actually verified as passing under my current Tencent Enterprise Email configuration.

Different Tencent email products, legacy configurations, or other sending services may not have the exact same SPF content, so you shouldn’t just blindly copy this after reading this article.

What you should really reference is the configuration of the email service you actually use, and verify it through real delivery.

Additionally, I modified the existing SPF TXT record instead of adding a second SPF record.

Because having multiple SPF records for the same domain can ironically cause new PermError.

After the modification, Cloudflare now shows:

Plaintext
v=spf1 include:spf.mail.qq.com ~all

Meanwhile, the original DKIM record:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

still exists.

[Figure 6: Corrected SPF in Cloudflare, along with the pre-existing DKIM record]
[Figure 6: Corrected SPF in Cloudflare, along with the pre-existing DKIM record]

Verified again via command:

Plaintext
dig TXT shuijingwanwq.com +short

The result is:

Plaintext
"v=spf1 include:spf.mail.qq.com ~all"
"google-site-verification=..."

But after the previous misjudgment, I was no longer planning to consider the matter closed just by seeing the DNS was correct.

So I proceeded to do a second real email test.


9. Second Email Send: SPF, DKIM, DMARC All PASS

After modifying the SPF, I again sent from:

Plaintext
admin@shuijingwanwq.com

to send a new email to Gmail.

I went back into:

Show original

This time, the result had become:

Plaintext
SPF: PASS
DKIM: PASS
DMARC: PASS
[Figure 7: After fixing SPF, Gmail actual verification shows SPF, DKIM, DMARC all PASS]
[Figure 7: After fixing SPF, Gmail actual verification shows SPF, DKIM, DMARC all PASS]

At this point, I considered the email authentication issue truly resolved.

The final result:

ItemResult
SPFPASS
DKIMPASS
DMARCPASS

Among these, DKIM was actually fine the whole time.

What was actually modified was the SPF:

Plaintext
include:mail.qq.com

changed to:

Plaintext
include:spf.mail.qq.com

Only then did Gmail’s actual authentication change from:

Plaintext
SPF: PERMERROR

to:

Plaintext
SPF: PASS

10. Why Did DMARC Still PASS When SPF Failed?

There was another confusing point during the first test:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS

Why did DMARC still pass when SPF had a problem?

Because DMARC does not require:

Plaintext
SPF PASS
and
DKIM PASS

DMARC can be verified through either a compliant SPF or DKIM path, and it also requires domain alignment to be satisfied.

In my email:

Plaintext
DKIM: PASS
d=shuijingwanwq.com

And the domain used by the sender From was also:

Plaintext
shuijingwanwq.com

Therefore, DKIM already satisfied the authentication and domain alignment conditions required by DMARC.

So even if SPF encountered:

Plaintext
PERMERROR

DMARC could still:

Plaintext
PASS

This also shows that:

Just seeing DMARC PASS does not mean you can deduce that both SPF and DKIM are necessarily fine.

It is best to check them separately.


11. Re-testing: Score Increased from 71 to 83

After the real issues were resolved, I ran TCPLab again.

The new comprehensive score became:

83 points.

Still Grade B, but already higher than the initial:

Plaintext
71 points

It improved by:

Plaintext
12 points
[Figure 8: Re-running TCPLab after fixes, comprehensive score increased from 71 to 83]
[Figure 8: Re-running TCPLab after fixes, comprehensive score increased from 71 to 83]

This time it could already be seen that:

  • HTTP → HTTPS forced redirect was recognized;
  • SPF was normal;
  • DMARC now existed;
  • HTTPS / TLS was normal;
  • HSTS was normal.

But TCPLab still showed:

DKIM not detected.

I did not continue to address this at that point.

Because actual email delivery had already explicitly proven:

Plaintext
DKIM: PASS

And it could be mutually verified from both the original email and DNS:

Plaintext
d=shuijingwanwq.com
s=xacy2606

as well as:

Plaintext
xacy2606._domainkey.shuijingwanwq.com

So in this situation, the actual delivery result is more meaningful for reference than whether a scanner can find the selector.


12. I Also Went Back to the Original Post to Add Corrections

Before this troubleshooting, I had discussed this TCPLab test result in a post.

At that time, because I hadn’t done real email verification yet, my judgment regarding SPF / DKIM was incomplete.

After confirming the actual situation later, I went back to the original post and added:

  • SPF actually encountering PERMERROR;
  • The correctly modified SPF;
  • DKIM actually being PASS all along;
  • Gmail ultimately passing all three items;

these follow-up results.

[Figure 9: Adding real email verification results to the original post and correcting previous incomplete judgments]
[Figure 9: Adding real email verification results to the original post and correcting previous incomplete judgments]

I felt this step was still necessary.

In technical records, it is quite normal for initial judgments to be inaccurate.

The key is that after obtaining new evidence later, you should promptly update the old conclusions to make them complete.

This is more important than stubbornly maintaining the initial judgment.


13. Why Didn’t I Continue Processing All Remaining Items for the Score?

After re-testing, there were still some items losing points.

For example:

  • DNSSEC;
  • CAA;
  • Some web security response headers;
  • A stricter DMARC policy;
  • DKIM, which TCPLab still did not detect.

I did not continue to process all of them.

The reason is not that these things are worthless.

But rather:

Their current necessity for resolution is not on the same level as the issues that were actually fixed.

For example, HTTP → HTTPS returning an incorrect status, or a misconfigured SPF, are existing problems that can be explicitly verified.

DNSSEC, CAA, and CSP, however, are more about further enhancing security on top of currently normal operations.

To push the 83 points higher by adding extra configuration, maintenance costs, and even potential failure risks seemed unnecessary to me.

So I stopped here this time.


14. DNSSEC: Valuable, but Not Necessary to Handle Right Now

DNSSEC can provide additional authenticity and integrity verification for DNS data.

This is certainly a valuable security capability.

However, it usually involves DS record configuration between the DNS provider and the domain registrar.

If configured incorrectly, it can directly affect domain resolution in severe cases.

So I wouldn’t temporarily enable it just because of a deduction in a test report.

If I decide to configure DNSSEC in the future, I would prefer to treat it as an independent task for complete verification, rather than just casually flipping a switch.


15. CAA: Can Be Done, but No Need to Do It Now

CAA is used to restrict which CAs can issue certificates for a domain.

It also has security value.

However, not having CAA does not mean current HTTPS certificates are insecure, nor does it mean the website has a malfunction.

And my website also involves services like Cloudflare and EdgeOne simultaneously.

If I were to configure CAA in the future, a more reasonable approach would be to first confirm the actual certificate issuance and auto-renewal methods, and then set the corresponding rules.

Directly copying CAA records just to gain a few points is not the approach I want to take.


16. Web Security Response Headers: Don’t Add Them Mechanically for the Score

The report also included suggestions for response headers like:

Plaintext
Content-Security-Policy
X-Content-Type-Options
X-Frame-Options
Referrer-Policy

Some of these are indeed worth optimizing later.

But especially something like:

Plaintext
Content-Security-Policy

If the website simultaneously uses:

  • WordPress;
  • Google Ads;
  • Third-party analytics;
  • External JavaScript;
  • CDN;
  • External images and fonts;

then an incorrectly configured CSP can easily directly cause page functionality issues.

So I won’t just add all response headers at once to turn:

Plaintext
0/5

into:

Plaintext
5/5

in the test results.

These can be reviewed individually later.

There is no need to add extra risk and workload for the sake of a score right now.


17. DMARC Temporarily Stays at p=none

My current DMARC is:

Plaintext
v=DMARC1; p=none

Meaning, I am enabling authentication and observation first.

After confirming that all legitimate email sources are stable long-term, I can then consider:

Plaintext
p=quarantine

or even:

Plaintext
p=reject

But I just discovered and fixed an SPF configuration error through real emails.

Immediately raising DMARC to the strictest policy right now doesn’t seem very necessary.

I’ll let it run stably for a while first.


18. The True Value This Time Wasn’t the Score Going from 71 to 83

If you only look at the result:

Plaintext
71 → 83

Then this might just be a very ordinary “website security score optimization record”.

But I feel what is truly worth recording is not these 12 points.

But rather the difference brought by this verification method.

The online test alerted me to:

Plaintext
DKIM not detected

But the real email told me:

Plaintext
SPF: PERMERROR
DKIM: PASS
DMARC: PASS

Then, after fixing the genuinely problematic SPF:

Plaintext
SPF: PASS
DKIM: PASS
DMARC: PASS

Meanwhile, TCPLab still did not detect DKIM.

This actually illustrates a point quite well:

Automated testing tools are suitable for finding problem clues, but not as the sole source of truth.

Especially for email authentication.

Often, the most direct verification method is not complicated.

Actually send an email.

Then see how the server actually responsible for receiving the email authenticates it.


19. How I Will Check Email Authentication in the Future

After this experience, if I check a domain’s email authentication in the future, I will probably follow this order.

First, check the DNS:

Plaintext
dig MX example.com +short
dig TXT example.com +short
dig TXT _dmarc.example.com +short

Then actually send an email from that domain’s mailbox to Gmail.

In Gmail, open:

More → Show original

First look at:

Plaintext
SPF
DKIM
DMARC

If there are any anomalies, then check:

Plaintext
Authentication-Results:

and:

Plaintext
DKIM-Signature:

In DKIM, focus on:

Plaintext
d=
s=

Where:

Plaintext
d=

represents the DKIM signing domain.

Plaintext
s=

is the selector.

Only with the selector do you know what you should actually be querying:

Plaintext
selector._domainkey.example.com

Instead of just immediately concluding that DKIM is not configured just because an online tool shows:

Plaintext
DKIM not detected

and assuming DKIM is not configured.


20. Final Results

After completing the actual fixes this time, the current status is:

ItemCurrent Status
Domain renewalRenewal completed after check
HTTP → HTTPS301 forced redirect restored via EdgeOne
HTTPS / TLSNormal
HSTSNormal
SPFCorrected, Gmail actual test PASS
DKIMOriginally normal, Gmail actual test PASS
DMARCAdded p=none, Gmail actual test PASS
DNSSECNot handling for now
CAANot handling for now
CSP and other response headersNot adding configurations for the score for now

TCPLab comprehensive score:

Plaintext
Initial: 71 points
Final: 83 points

But compared to this:

Plaintext
83 points

the final result I care more about is actually:

Plaintext
SPF:   PASS
DKIM:  PASS
DMARC: PASS

Because this is the authentication result given by Gmail, which is actually responsible for receiving emails, after a real email delivery.


Finally

This experience made me feel once again that:

Security testing tools are very valuable.

They can quickly tell us:

Which areas are worth checking.

But they cannot completely replace our judgment on:

Which problems truly need to be solved.

Some items are indeed broken and should be fixed immediately.

Some belong to further hardening, and there is no need to add extra workload for the sake of a score right now.

And some might just be cases where the testing tool itself failed to recognize them.

So in the end, I did not continue pursuing the so-called:

Plaintext
100 points

but rather stopped after confirming that:

  • The domain renewal was completed;
  • HTTP had been restored to the 301 redirect to HTTPS;
  • TLS was normal;
  • SPF was normal;
  • DKIM was normal;
  • DMARC was normal;

I then stopped.

Solving the real problems is more important than turning every item on the test page green.

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系