We may earn a referral fee at no additional cost to you when you shop through the links on this page.
If your Cloudflare-proxied Apache site is set to Flexible SSL/TLS mode, the leg between Cloudflare’s edge and your origin is plaintext HTTP, even though the visitor’s browser sees a green padlock. This DevAnswers guide closes that gap: install a Cloudflare Origin CA certificate on Apache, set Cloudflare to Full (strict) so the cert actually gets validated, and optionally layer on Authenticated Origin Pulls so the origin only accepts traffic that came from Cloudflare.
Why You Need an Origin Certificate
Per Cloudflare’s own documentation, Flexible mode is “specifically designed for origin servers that do not have an SSL certificate installed or do not support TLS.” That’s the historical use case, not a security posture you want to keep. In Flexible, traffic between Cloudflare and your origin “communicates with the origin server over unencrypted HTTP.” Cloudflare’s docs put the consequence bluntly: “Setting your encryption mode to Flexible makes your site partially secure.” An Origin CA certificate is the simplest fix: Cloudflare issues you a certificate signed by its private root CA, you install it on Apache, and Cloudflare validates it on the back end (provided you also set the right encryption mode, which is the step most tutorials skip).
Prerequisites
sudo may not be installed by default. Either install it (as root: apt install sudo, then add your user to the sudo group), or run every command in this guide as the root user and drop the sudo prefix. All commands below assume sudo works. - Apache 2.4 already installed and serving the domain over HTTP. This guide does not cover initial Apache install.
- The domain is on Cloudflare with proxy enabled (orange cloud). Verify by checking that the site loads through Cloudflare’s edge.
- Root or sudo access on the origin server to write
/etc/ssl/cloudflare/files, edit the vhost config, and reload Apache. - Apache
mod_sslmodule available. It’s preinstalled on every supported Ubuntu/Debianapache2package;a2enmod sslbelow would fail otherwise.
Origin CA vs Let’s Encrypt vs Authenticated Origin Pulls
Three certificate strategies show up for sites behind Cloudflare. Pick the one that matches your situation:
- Cloudflare Origin CA: simplest. Free, generated in the Cloudflare dashboard with validity options up to 15 years. Signed by Cloudflare’s private root CA, so it’s only trusted by Cloudflare’s edge, not by browsers. Pause Cloudflare and visitors hitting the origin directly will get untrusted-certificate errors. Worse, once browsers have cached the HSTS header from a normal HTTPS visit (this guide sets a one-year HSTS below), those errors become hard blocks with no click-through option until HSTS expires. Plan any direct-to-origin debugging accordingly. Use this when traffic always flows through Cloudflare’s proxy.
- Let’s Encrypt: free, browser-trusted, 90-day rotation (so you’ll need certbot or similar automation). Use this if you want the option to bypass Cloudflare for debugging or to serve directly without scary warnings.
- Authenticated Origin Pulls (AOP): not a replacement, a layer on top of either of the above. Cloudflare presents a CF-issued client certificate when proxying to your origin, and Apache rejects any request that doesn’t carry it. Covered later in this guide.
Generate the Origin CA Certificate in Cloudflare
In the Cloudflare dashboard, select your domain, then go to SSL/TLS → Origin Server → the Origin Certificates tab, and click Create Certificate. The form lets you choose the private key type. Cloudflare’s dashboard labels the two options as RSA and ECC (the elliptic-curve option, sometimes called ECDSA elsewhere): ECC produces a smaller, faster-handshake cert; RSA is the more compatible default. Pick the validity length you want; Cloudflare offers options up to many years out. Click Create.
The next screen displays the certificate and private key in PEM format. Copy both into a temporary scratchpad on your local machine before closing the modal. Cloudflare will not show the private key again after you close the dialog. You’re about to paste them into files on your origin server in the next section.
Install the Certificate and Key on Your Server
SSH into your origin server. Create a dedicated directory for Cloudflare-issued certificates so they live alongside your other SSL material under /etc/ssl/:
sudo mkdir -p /etc/ssl/cloudflare/
Open a new file for the certificate. Replace example.com with your actual domain throughout:
sudo nano /etc/ssl/cloudflare/example.com.pem
Paste in the Origin Certificate from the Cloudflare modal, save, and exit (CTRL+X, Y, ENTER). For the private key, pre-create the file with 0600 permissions BEFORE opening it in nano. This avoids the small window where the key would otherwise exist with default world-readable permissions between file creation and the chmod step below. The install command creates an empty file with the mode you specify; nano then edits the existing file and preserves its mode bits on save:
sudo install -m 0600 /dev/null /etc/ssl/cloudflare/example.com.key
sudo nano /etc/ssl/cloudflare/example.com.key
Tighten permissions on the private key. The Apache parent process starts as root (it has to, to bind port 443 and read keys), then drops privileges to www-data; this means root needs to be able to read the key but nothing else does. See our Linux file permissions overview if 0600 root:root needs unpacking:
sudo chmod 0600 /etc/ssl/cloudflare/example.com.key
sudo chmod 0644 /etc/ssl/cloudflare/example.com.pem
sudo chown root:root /etc/ssl/cloudflare/example.com.*
Configure the Apache VirtualHost (with TLS Hardening)
Enable Apache’s SSL module, the rewrite module (used for redirecting HTTP to HTTPS), and the headers module (used for the HSTS header below):
sudo a2enmod ssl
sudo a2enmod rewrite
sudo a2enmod headers
Open your site’s vhost file under /etc/apache2/sites-available/ (Ubuntu/Debian convention). Add a :443 VirtualHost block alongside your existing :80 block. The configuration below points Apache at the Cloudflare-issued certificate and key, applies modern mod_ssl hardening (TLS 1.2 + 1.3 only, a Mozilla SSL Config Generator-style cipher suite, and an HSTS header), and leaves the :80 block as plain HTTP for now. We’ll add the HTTP-to-HTTPS redirect later, AFTER Cloudflare is on Full (strict), to avoid the redirect-loop trap:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
ErrorLog ${APACHE_LOG_DIR}/example.com_error.log
CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
SSLEngine on
SSLCertificateFile /etc/ssl/cloudflare/example.com.pem
SSLCertificateKeyFile /etc/ssl/cloudflare/example.com.key
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
Save and exit (press CTRL + X, press Y, then press ENTER)
Enable the site, check the syntax, and reload Apache. The apachectl control script returns exit code 0 on success and a value greater than 0 on any error (including syntax errors caught by configtest), so chaining with && ensures we only reload on a clean config:
sudo a2ensite example.com.conf
sudo apachectl configtest && sudo systemctl reload apache2
This reload is safe even though Cloudflare is still on Flexible mode. The :80 block we wrote has no redirect logic, so Cloudflare keeps hitting :80 over plain HTTP and serving visitors normally. The new :443 block is now ready for Cloudflare to validate in the next step. Use reload rather than restart; a graceful reload doesn’t abort currently open connections, so live visitors don’t see a momentary blip while the new SSL config takes effect.
Set Cloudflare to Full (strict): The Step Most Tutorials Skip
This is the step that turns the previous fifteen minutes of work into actual security. Without it, you have an Origin CA certificate installed on Apache and a Cloudflare proxy that doesn’t bother validating it. In the Cloudflare dashboard, go to SSL/TLS → Overview and set the encryption mode to Full (strict).
Cloudflare’s encryption modes mean different things. Off serves everything over plain HTTP. Flexible, as covered above, encrypts visitor-to-Cloudflare but not Cloudflare-to-origin. Full encrypts both legs but accepts any origin certificate, including expired or self-signed ones, useful for testing, useless for trust. Full (strict) validates the origin certificate against Cloudflare’s accepted CAs (which includes the private root that signed your Origin CA cert). There is also a newer Strict (SSL-Only Origin Pull) mode that requires SSL from the start of the request flow; it’s stricter than Full (strict) and aimed at zero-tolerance setups. For an Origin CA install, Full (strict) is the right choice; only it actually delivers what most readers think they’re getting when they install an origin certificate.
Add the HTTP-to-HTTPS Redirect (Safe Now)
Now that Cloudflare is on Full (strict), the leg between Cloudflare and your origin is encrypted and validated end-to-end. You can safely add the HTTP-to-HTTPS redirect to your :80 vhost so any direct-to-origin or non-proxied traffic gets bumped up to HTTPS. Cloudflare-proxied HTTPS visitors won’t hit the redirect at all because Cloudflare connects to :443 directly under Full (strict).
Edit the same vhost file and add three RewriteEngine lines INSIDE the existing :80 block:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public_html
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Save and exit (press CTRL + X, press Y, then press ENTER)
Reload Apache again to activate the redirect:
sudo apachectl configtest && sudo systemctl reload apache2
This second reload is safe because Cloudflare is now on Full (strict): HTTPS visitors traverse Cloudflare-to-origin over :443 directly, bypassing the :80 redirect block entirely. Direct-to-origin HTTP visitors (or anyone hitting :80 outside Cloudflare) get the 301 to HTTPS and complete the loop normally without the Flexible-mode loop trap.
Verify the Setup End-to-End
Two checks. First, hit your site in a browser via https://example.com and confirm the padlock + working content. The browser sees Cloudflare’s edge certificate (which is browser-trusted), not your Origin CA cert, so the lock icon is normal.
Second, prove the origin leg is actually serving the Origin CA cert by bypassing Cloudflare directly. The curl --resolve flag overrides DNS for one request and forces it to go to a specific IP. Cloudflare’s docs describe it as “a sort of /etc/hosts alternative provided on the command line.” Replace 1.2.3.4 with your origin’s public IP:
curl -kvI --resolve example.com:443:1.2.3.4 https://example.com/
The -k tells curl to accept the untrusted Origin CA cert (it isn’t browser-trusted, remember). The -v output will show the TLS handshake against the cert you installed; look for issuer: O=Cloudflare, Inc.; CN=Cloudflare Origin CA in the certificate chain. If you see a generic browser-trusted CA there instead, you’ve hit Cloudflare’s edge somehow and the --resolve override isn’t doing its job; double-check the IP.
Optional: Layer on Authenticated Origin Pulls
You now have encryption between Cloudflare and your origin. The next-tier guarantee is making your origin only accept connections that came from Cloudflare. Cloudflare’s docs describe Authenticated Origin Pulls as: “When you enable global Authenticated Origin Pulls (AOP), Cloudflare uses a Cloudflare-provided client certificate for all proxied traffic to your zone.” That certificate is shared across all Cloudflare accounts and “guarantees that the request is coming from the Cloudflare network.” Your Apache can be configured to verify it via SSLVerifyClient + SSLCACertificateFile pointing at the CF origin-pull CA bundle (downloadable from Cloudflare’s docs).
One important constraint: AOP “does not apply when your SSL/TLS encryption mode is set to Off or Flexible”. You must be on Full or Full (strict) for AOP to take effect. Since you set Full (strict) in the previous section, you’re good. Enable AOP in SSL/TLS → Origin Server → Authenticated Origin Pulls, download Cloudflare’s origin-pull CA bundle from their docs, save it to /etc/ssl/cloudflare/origin-pull-ca.pem, and add the verification block inside your :443 VirtualHost:
# Inside the existing <VirtualHost *:443> block, alongside the SSL directives:
SSLVerifyClient require
SSLVerifyDepth 1
SSLCACertificateFile /etc/ssl/cloudflare/origin-pull-ca.pem
Save and exit (press CTRL + X, press Y, then press ENTER)
After saving, run sudo apachectl configtest && sudo systemctl reload apache2 to apply. Apache will now refuse any TLS connection that doesn’t present the Cloudflare origin-pull client certificate, locking your origin to Cloudflare-only traffic.
Troubleshooting
Common errors and what they mean:
- Untrusted-certificate warning when bypassing Cloudflare for debugging. Expected: Origin CA certificates are not trusted by client browsers when accessing the origin directly outside of Cloudflare. Pausing or disabling Cloudflare on a subdomain that uses an Origin CA certificate causes untrusted-certificate errors for site visitors. Important: because this guide sets HSTS with a one-year
max-age, any browser that has previously visited your site through Cloudflare will treat the untrusted cert as a hard block with no click-through. Usecurl -k --resolve example.com:443:1.2.3.4 https://example.com/for direct-to-origin tests, or use a browser profile that has never visited the site before. - Apache fails to start with “Permission denied” reading the key. The 0600 root:root permissions assume Apache reads the key as root before dropping privileges. Confirm with
ls -l /etc/ssl/cloudflare/; the key should be-rw-------owned byroot:root. If the file is somehow owned bywww-datawith restrictive perms, root can still read it, but a chmod typo can lock everyone out; recheck the chmod line. - Cloudflare 526 (Invalid SSL Certificate) error. Means Full (strict) is on but Cloudflare can’t validate your origin cert. Either you used a self-signed cert (use Full instead, or switch to Origin CA), or the cert/key pair on Apache don’t match each other (regenerate from Cloudflare).
- Apache logs show every visitor as a Cloudflare IP. Expected when proxying through Cloudflare. To recover the real client IP, see our Get the real client IP from Cloudflare in Apache or PHP guide.
Where to go from here
Your Apache origin now serves over HTTPS to Cloudflare’s edge with end-to-end encryption that’s actually validated. The mechanics work the same on a self-hosted box or a managed-hosting platform like a Cloudways server. If you set up the host fresh and need a refresher on remote access, our connect to a Linux server with SSH guide covers reliable SSH setup. For Apache+PHP setups, our find and edit php.ini on Linux and Windows reference is in the same operational toolkit. The next thing most sysadmins want to harden after this is rate limiting and bot mitigation, both of which Cloudflare provides at the edge, so make sure your Apache logs are correctly recording the original visitor IP for any rule you write.
Excellent guide Clear, concise and specific. Thank you.👍👌🤩
a2enmod rewrite
systemctl restart apache2
ill echo Seb’s feelings – been killing myself trying to get this done and this was so painless. I was in a rabbit hole of atrocious cloudflare documentation. Where’s the customer service? Link upon link within links – a never ending blackhole of links. I must have had 30 tabs open, by the end of it all I forgot what I was doing.
What really killed me was the link to a paid ssl service…really?
Thank for this!
I can finally stop beating my head against the brick wall over and over and over, THANK YOU! That was starting to hurt.
Hi! I followed your very clear and helpful instructions however I still get a 522 error. I’ve already configured ufw to allow CF servers through. Any advice would be greatly appreciated.
I completed the steps you outlined. But I am getting the below ERR_CERT_AUTHORITY_INVALID error for my site. What am I missing?
Your connection is not private
Attackers might be trying to steal your information (for example, passwords, messages, or credit cards). Learn more
NET::ERR_CERT_AUTHORITY_INVALID
Hi,
Great Tutorial (as always).
For a person with very limited technical understanding of Unix based VPS configurations, your step by step LEMP stack configuration for Ubuntu 20.04 droplet made the entire process a cakewalk for me and in the process taught many features/ aspects that i could not have learned even after watching many youtube tutorails/ reading blogs.
My heartfelt gratitude and sincere appreciation for the very comprehensive (& precise) posts. Keep it up.
Also, two requests
1) Post on Cloudflare configuration steps for Nginx
2) If you also cover wordpress then posts on wordpress migration from shared hosting to Digitalocean and troubleshooting etc.
Many thanks for the comment!
Thanks a lot for your work and support.
I have an additional question: the certificate between Cloudflare and my server is valid 15 years but the certificate between Cloudflare and a user browser is valid 1 year. What will happen when this year will gone? The certificate will be automatically renew or will I have to do someting to renew it?
Cloudflare should renew that for you automatically.
how can I test if it is working?
sudo tail /var/log/apache2/access.log -fTrès très très bien, trop facile ! Merci
(Very very very good, too easy !)
I have a problem. I’ve got handshake error, and i dont know where to check the error.
anyone know how to include the root origin cert? how do we use it when cloudflare already generated the normal certificate.
do we add it to the end of that file ? any help much appreciated
i’m looking for it. you found answer ?
AH00526: Syntax error on line 26 of /etc/apache2/sites-enabled/example.conf:
Invalid command ‘SSLEngine’, perhaps misspelled or defined by a module not included in the server configuration
Action ‘configtest’ failed.
Run
sudo a2enmod sslI get as far as the virtual host bit and when I type the command it opens nano and it is blank..anybody help or know why..
Thanks
i occured error 525 ssl handshake failed
i have open 433 port how can i resolve this!?
Hi, I ran into the same problem. I had to run “a2ensite xxx.conf” (the conf file you edited). It enables the VirtualHost, and also creates a conf file in the “sites-enabled” directory. After just that, boom, worked.
Thank you so much for helping sir
Great walkthrough. On the final step I was running into a “Invalid command ‘SSLEngine'” error, needed to run “sudo a2enmod ssl” to fix this.
Great, thanks for that.