How to Fix HTTPSConnectionPool SSL Errors When Using Python Request

Today, I want to share an experience I had recently while working on a Python project that uses IBM Watson’s Tone Analyzer API to analyze the emotions behind tweets. Everything was running smoothly initially I was able to get tone analysis for a list of 1000 tweets in a neat JSON format. But suddenly, I started running into this frustrating error:

Error Code

: HTTPSConnectionPool(host='api.eu-gb.tone-analyzer.watson.cloud.ibm.com', port=443): 
Max retries exceeded with url: /instances/148003f6-4092-49e6-bf71-2b48ffd0a55d/v3/tone?version=2017-09-21
(Caused by SSLError(SSLError("bad handshake: SysCallError(-1, 'Unexpected EOF')")))

If you’ve faced something similar, keep reading I’ll explain what this error means, how I understood the root causes, and how I improved my code to make it more reliable and easier to maintain.

Explain the Error

This error essentially points to a problem during the SSL/TLS handshake, which is the process your Python script uses to securely connect to IBM Watson’s API over HTTPS.

  • SSLError means the secure connection setup failed.
  • Max retries exceeded means the code tried several times but couldn’t establish the connection.
  • The message “bad handshake: SysCallError(-1, ‘Unexpected EOF’)” means the server closed the connection unexpectedly during the handshake process.

Common reasons this might happen include:

  • Unstable or slow internet connection.
  • Outdated SSL libraries in your Python environment.
  • Firewalls or proxies blocking secure connections.
  • Server-side temporary issues or downtime.
  • Too many rapid API requests causing throttling.
  • Expired or invalid SSL certificates.

How I Fix and Troubleshot This Issue

Here’s what worked for me and what I suggest trying if you face this error:

  • Check your network: Make sure your internet connection is stable and no firewall or VPN is blocking SSL connections.
  • Update your Python packages: I ran the following command to update core packages that handle HTTPS connections:
install --upgrade requests urllib3 certifi
  • Verify your Python version: Older Python versions might have outdated SSL support.
  • Add retry logic with delays: Rather than hammering the API repeatedly, I implemented a retry mechanism with exponential backoff to handle temporary failures.
  • Check IBM Watson’s status: Sometimes the service itself might be down or slow.
  • Reduce API call frequency: I added small delays between calls to avoid throttling.
  • Test SSL connection manually: Using tools like openssl or Python’s SSL debug flags helped confirm if the issue was environmental.

Review My Original Code

My initial code looked like this: it loops over a list of tweets (l3), sends each tweet’s text to the Watson API, parses the tone results, and accumulates emotion scores in a dictionary called emotions_score3.

Some key issues I noticed:

  • I reused the variable i in both outer and inner loops, which can cause bugs or confusion.
  • When summing scores, I always took the score of the first tone (k[0]['score']) regardless of which tone I was processing.
  • No error handling: if the API request failed once, the whole script would stop.
  • No logging or print statements to track progress.
  • I converted the API response to JSON string and then back to dictionary unnecessarily.
  • No delay between requests, which can cause connection resets.
  • The list lockdown3 was used but never initialized or explained.

Improve Code with Error Handling and Logging

Here’s how I improved the script to be more robust:

json
import time
import logging
from ibm_watson import ToneAnalyzerV3
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
from requests.exceptions import SSLError, ConnectionError, Timeout

# Setup logging to track progress and issues
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Initialize IBM Watson Tone Analyzer
authenticator = IAMAuthenticator('{API KEY}') # Replace with your API key
tone_analyzer = ToneAnalyzerV3(
version='2017-09-21',
authenticator=authenticator)
tone_analyzer.set_service_url('{URL}') # Replace with your URL

emotions_score3 = {}
lockdown3 = []

def analyze_tone_with_retry(text, retries=3, backoff=2):
"""Send text to API with retry logic for network errors."""
for attempt in range(retries):
try:
response = tone_analyzer.tone({'text': text}, content_type='application/json').get_result()
return response
except (SSLError, ConnectionError, Timeout) as e:
logging.warning(f"Attempt {attempt+1} failed: {e}")
if attempt < retries - 1:
time.sleep(backoff ** attempt) # Exponential backoff delay
else:
logging.error(f"All {retries} attempts failed for this text.")
return None

for idx, text in enumerate(l3):
logging.info(f"Analyzing tweet {idx+1} out of {len(l3)}")
tone_analysis = analyze_tone_with_retry(text)
if not tone_analysis:
continue # Skip this tweet on persistent failure

tones = tone_analysis.get("document_tone", {}).get("tones", [])
if tones:
for tone in tones:
tone_name = tone.get('tone_name')
tone_score = tone.get('score', 0)
lockdown3.append(tone_name)
emotions_score3[tone_name] = emotions_score3.get(tone_name, 0) + tone_score
else:
logging.info("No tones detected for this tweet.")

# Print aggregated scores
print("Aggregated Emotion Scores:")
for tone_name, score in emotions_score3.items():
print(f"{tone_name}: {score:.4f}")

What Improve?

  • Added retry logic with exponential backoff for network resilience.
  • Used logging for progress tracking and error reporting.
  • Clear variable names and avoided reusing loop variables.
  • Correctly summed the score for each specific tone.
  • Gracefully skipped tweets that keep failing instead of stopping the whole script.
  • Removed unnecessary JSON stringify and parse steps.
  • Provided progress info for easier debugging.

Final Thought

Getting SSL errors like the one I faced can be confusing, especially if your code was working fine before. Most of the time, these errors come from temporary network issues, outdated libraries, or server-side throttling. By updating my environment, adding proper error handling, and respecting API rate limits with retry mechanisms, I managed to make my script much more reliable.

If you are new to Python or API integration, don’t worry these issues happen to everyone. The key is to handle exceptions gracefully, log enough information to understand what’s going on, and avoid hard crashes when things go wrong.

Related blog posts