SSL certificate problem on Windows

Issue Summary

The same issue as the following, however, on Windows the solution does not work:

Despite my site having an SSL enabled on Local, I still have issues such as below:
AWS HTTP error: cURL error 60: SSL certificate problem: unable to get local issuer certificate

Because it is a Windows server when I SSH in, the usual Ubunto commands do not work such as apt-get.

I also noticed that while the SSL is enabled and seems to work when regularly viewing the site, Local by Flywheel still prompts me to “Trust” it, unsure if this is related.

Troubleshooting Questions

  • Does this happen for all sites in Local, or just one in particular? All.

  • Are you able to create a new, plain WordPress site in Local and access it in a Browser? Yes.

Replication

When connecting to AWS S3 service. The easiest way to replicate is by Upload Your WordPress Media to Amazon S3 with WP Offload Media - Delicious Brains Inc.

System Details

  • Which version of Local is being used? 5.9.0+4961

  • What Operating System (OS) and OS version is being used?

    • Microsoft Windows 10 Home
  • Local Log? local-lightning.log (102.1 KB)

2 Likes

Hey @clay, is there any chance you can chip in on this? I found very similar issues across the web, but none of the solutions is specific to Local by Flywheel: https://stackoverflow.com/questions/29822686/curl-error-60-ssl-certificate-unable-to-get-local-issuer-certificate

Any sites using AWS solutions like S3 will likely have the same problem, and I really want to avoid WAMP :joy:

Best,
Elliott

Since I was testing with Media Offload, I figured I’ll test with an alternative solution such as Media Cloud.

And… Same problem!

I have almost 100 sites on Local, but with this being a constant burden I genuinely need a solution for local SSLs.

I’m in the process of installing MAMP/WAMP as we speak.

Thanks

This is definitely going to be a tricky one to solve for. I’m curious, when you installed MAMP/WAMP, did that fix things?

Here’s what I think the general issue is:

  1. Your browser can connect to the Local site over HTTPS because the Local site’s SSL cert was registered with the browser.
  2. The Local Site (ie, the PHP running WordPress) is having issues making a secure connection to AWS because it doesn’t know to trust the cert from xxxx.amazonaws.com

I’ve never had to dive into this too deeply, but I see that WP comes bundled with certificates:

To troubleshoot this more, can you try:

  1. Take a backup of the site!
  2. Navigate here to download the cacert.pem file that most of the internet uses for this stuff:
  3. Replace WordPress’ ca-bundle.crt file with the recently downloaded cacert.pem. To be specific:
    • Delete WordPress’ ca-bundle.crt
    • Move cacert.pem file to wp-includes/certificates/caceert.pem
    • Rename wp-includes/certificates/caceert.pem to wp-includes/certificates/ca-bundle.crt

To give you a broader idea of what we’re doing, we’re basically updating the “known certs” that WordPress knows about and hopefully, there are some updated ones for AWS in there.

Since this is so specific to the certs that AWS uses, do you have a direct link to one of the assets you are trying to offload so that I can try to replicate a secure connection from within one of my Local sites?

Hello Ben,

Thank you for offering this solution. I just tried replacement ca-bundle.crt with a renamed copy of the caceert.pem file, but unfortunately, that does not resolve the issue.

I suppose the easiest method to replicate the issue is to install a plugin such as WP Offload Media Lite or Media Cloud that connects to AWS S3.

I have tried both of the above, which work fine on a server but not on Local.

Here is the error in WP Offload Media:

AWS HTTP error: cURL error 60: SSL certificate problem: unable to get local issuer certificate (see libcurl - Error Codes)

Screenshot 2020-12-03 094339

Similarly, I cannot connect on Media Cloud despite connecting fine from a server.

Thank you, I really appreciate the guidance on this as I have tonnes of sites running on Local that connect to AWS S3 for the media library (which is becoming more-and-more standard).

Best,
Elliott

I am not sure if this helps, but if I output:

print_r( openssl_get_cert_locations() );

I get the following:

Screenshot 2020-12-03 094339

This suggests that PHP/Guzzle is looking for certificates in my C:\Program Files\Common File\SSL\cert.pem, opposed to WordPress’s included cert. I tried renaming and adding the ca-bundle.crt to that directory and restart Local, but it still hasn’t resolved the issue.

I based the above find on this suggestion on StackOverflow.

Perhaps there is some way of incorporating the contents of ca-bundle.crt into Flywheel’s own certificate?

1 Like

I have great news! I was able to fix the issue on a per-site basis, although going forward it would be good to know how it can be fixed sitewide.

I added the following line to the bottom of my \site-dir\conf\php\php.ini.hbs file and restarted the site:

openssl.cafile=C:\Program Files\Common Files\SSL\cert.pem

Obviously, this is based on the cert.pem (previously cacert.pem file you suggested) in the directory.

Based on this are you able to tell me how I can add that line to some kind of global PHP Ini file for my Local sites?

Thank you.

2 Likes

@ben.turner do you have any suggestions on editing a global PHP.INI file for Local, so I don’t have to do this per site? Thanks!

This is really good data to have and thank you for updating us with what you did to fix things!

If I’m understanding things correctly, that means that you’ve essentially told the WordPress site (specifically the php of the WordPress site) to use the host’s certificate bundle.

I took a closer look at the WP repo, and it looks like the ca-bundle.crt file will only be used if the request is made using one of the wp_remote_request functions:

In your case, with those plugins, they are using Guzzle and are likely crafting their own HTTP requests without using the built-in WP functions. The reason that your fix worked is that you are telling PHP and Guzzle to use the Host’s certs for the request.

To solve this long-term, I think there are two general paths:

  1. The plugin devs could refactor the requests so that they benefit from the fallbacks and guarding that WP comes with. That’s likely not going to happen soon, but it might be something they would be interested in to make things more robust.

  2. Local should bundle these certs so that lower-level requests can still be done over HTTPS.

I’d say #2 is definitely something that will make Local better, so I’ll put something in front of the dev team to take a closer look. I don’t have an ETA for when it will be worked on, so in the meantime, I’d say that you might be stuck having to manually add that line to the php.ini.hbs file.

Hey @ben.turner, this great thank you.

  1. Ha, yes I doubt it but I can present it to them at least!
  2. That would be fab long-term!

Best,

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.

I know that this topic is very old and closed, but because of how confusing all of this was (and is!), I thought I would follow up with more details about the problem, as well as how Local has implemented a fix in 6.5.2.

So basically, in vanilla PHP, there are two ways of making requests to third-party hosts over HTTPS:

  1. file_get_contents()
  2. cURL

Here’s a simple PHP file you can save to the site root to get a rough idea for how each of those types of requests work. Simply update the githubusername to your actual Github username in order for PHP to make successful requests:

<?php

// Get details about what PHP's openssl thinks about the certificates.
var_dump(openssl_get_cert_locations());

// save a url to an external HTTPS endpoint
$url = 'https://api.github.com/users/githubusername';

// ************************************************************
// Try using file_get_contents()
// ************************************************************
echo "<h2>Making requests with file_get_contents()</h2>";
// Create a stream with a custom header so that Github doesn't reject the request
// See: https://stackoverflow.com/a/39912696/6107112
//      https://stackoverflow.com/a/2107792/6107112
//      https://www.php.net/manual/en/function.file-get-contents.php#example-2207
$opts = [
    "http" => [
	"method" => "GET",
	"header" => "User-Agent: githubusername"
    ]
];
$response = file_get_contents($url, false, stream_context_create($opts));
var_dump(json_decode($response));


// ************************************************************
// Try using cURL
// ************************************************************
echo "<h2>Making requests with curl_exec()</h2>";

// initialize a handle
$ch = curl_init();

// set url
curl_setopt($ch, CURLOPT_URL, $url);

// When working with Github api, set a User-Agent header as my username. See: https://stackoverflow.com/a/39912696/6107112
curl_setopt($ch, CURLOPT_USERAGENT, "githubusername");

// return transfer as string
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// write the details of the request to a file so we can read it in
$errorFile = dirname(__FILE__) . 'curl_error.txt';
$out = fopen($errorFile, "w");

// be verbose about what's happening with the request
curl_setopt($ch, CURLOPT_VERBOSE, true);

// write stderr to our file
curl_setopt($ch, CURLOPT_STDERR, $out);

// make the actual request
$output = curl_exec($ch);

// close the handle and file
curl_close($ch);
fclose($out);

// print the contents of the file to our response buffer and delete the error file
echo "<pre>";
echo htmlspecialchars(file_get_contents($errorFile));
unlink($errorFile);
echo "</pre>";

Here’s a screenshot of what that page looks like for me in a browser:

Using the above PHP file is an interesting way to explore of the problem, but how is WordPress still able to make a secure connection over HTTPS despite a missing certificate? For example, how is WordPress still able to check for Core and Plugin updates as well as download new releases? The answer is that WordPress ships with it’s own certificates, and crafts its own cURL requests using the WP_Http->request method. This screenshot should give a rough idea of how that’s done:

So back to how Local is resolving this. We decided that the best thing would be to have these low-level php functions mimic the kind of requests that WordPress’ HTTP API is making. Towards that end, the latest version of Local and the latest builds of PHP (7.4.30, 8.0.22, 8.1.9) ship with a openssl.cafile configuration that points the certificate bundle included with WordPress.

Hope that’s interesting to others and helps your custom PHP request code work reliably over HTTPS!

4 Likes