ReadTimeout error when job tries to connect to Microsoft Entra ID

Verify that the workspace network configuration allows the "AzureActiveDirectory" service tag, and coordinate with the Azure Networking team to validate the setup.

Written by umakanth.charakanam

Last published at: October 24th, 2025

Problem

When your job attempts to connect to Microsoft Entra ID for authentication, you receive the following error.

ReadTimeout: HTTPSConnectionPool(host='login.windows.net', port=443): Read timed out. (read timeout=None)

 

Cause

The connection to login.windows.net has timed out. 

 

This domain resolves to a dynamic set of IP addresses that frequently change to support load balancing and redundancy. The domain login.windows.net is included in the "AzureActiveDirectory" service tag.

 

If your workspace network configuration allows only a specific, static list of IP addresses instead of the entire service tag, intermittent "ReadTimeout" errors may occur. 

 

Solution

Verify that the workspace network configuration allows the "AzureActiveDirectory" service tag, and coordinate with the Azure Networking team to validate the setup.

 

To verify, you can execute the following script in your workspace. It initiates up to 1000 consecutive connection attempts to login.windows.net, with a 5-second delay between each attempt. 

 

The script is configured to terminate immediately upon the first failed connection attempt and output the specific IP address involved. Confirm the IP is part of the "AzureActiveDirectory" service tag.

 

Then, verify the workspace network configuration allows this service tag. Contact the Azure Networking team to coordinate and validate the setup.

import requests
import socket
import time
import logging

# === CONFIGURATION ===
url = "https://login.windows.net"
hostname = "login.windows.net"
interval = 5  # seconds between requests
log_file = "connection_log.txt"

# === CLEAR LOG FILE ===
open(log_file, "w").close()

# === LOGGING SETUP ===
logging.basicConfig(
    filename=log_file,
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
)

def resolve_ip(host):
    try:
        return socket.gethostbyname(host)
    except socket.gaierror:
        return None

# === LOOP UP TO 1000 TIMES OR STOP ON FAILURE ===
for i in range(1000):
    ip = resolve_ip(hostname)
    if not ip:
        msg = f"Attempt {i + 1}: Failed to resolve {hostname}"
        print(msg)
        logging.error(msg)
        break

    msg = f"Attempt {i + 1}: Resolved {hostname} to {ip}"
    print(msg)
    logging.info(msg)

    try:
        response = requests.get(url, timeout=5)
        if response.status_code == 200:
            msg = f"Attempt {i + 1}: Success from IP {ip} - Status {response.status_code}"
            print(msg)
            logging.info(msg)
            time.sleep(interval)
        else:
            msg = f"Attempt {i + 1}: Failed from IP {ip} - Status {response.status_code}"
            print(msg)
            logging.warning(msg)
            break
    except requests.RequestException as e:
        msg = f"Attempt {i + 1}: Connection Error from IP {ip} - {e}"
        print(msg)
        logging.error(msg)
        break