EZ LearnEZ Learn
Courses
EZ Board
Dashboard
All articles
python-for-cyber-securitypythonexceptionserror-handling

Exception Handling: Scripts That Do Not Crash

A scan of a thousand hosts will meet one that refuses, one that does not resolve and one that times out. Exception handling is the safety net that lets the scan reach host 1,001.

Ashish Revar17 October 202614 min read
1 video lesson 5:19 total
Sign in to track your progress

Video lesson

A scan of a thousand hosts will meet at least one that refuses a connection, one whose name does not resolve, and one that times out. The network is not tidy, and a script that assumes it is will die at the first mess. This article is about writing scripts that expect trouble, log it, and carry on to host number 1,001.

Analogy: The safety net

A trapeze artist does not refuse to perform because a fall is possible; a net is strung below instead, so that a fall is survivable rather than fatal. Python's exception handling is that net for your code.

A port scan against a thousand hosts will meet at least one that refuses a connection, one whose DNS does not resolve, and one that times out. Without a net, the first such host ends the whole scan. With one, the script logs the problem and moves on to host 1,001.

try, except, finally

try:risky code runs hereErrorraised?yesnoexcept:handle the errorelse:no error, carry onfinally:always runs
Figure 14.1. The try, except, finally pattern. Code in finally runs whether or not an error occurred, which is why socket and file cleanup belongs there.

Watch the order in which the parts run, with a failing attempt and a successful one:

flow.py

def attempt(fail):
    try:
        print("  try: connecting")
        if fail:
            raise ConnectionRefusedError("refused")
    except ConnectionRefusedError:
        print("  except: logging the refusal")
    else:
        print("  else: it worked, carry on")
    finally:
        print("  finally: closing the socket")

print("a failing attempt:")
attempt(True)
print("a successful attempt:")
attempt(False)

Sample output

a failing attempt:
  try: connecting
  except: logging the refusal
  finally: closing the socket
a successful attempt:
  try: connecting
  else: it worked, carry on
  finally: closing the socket

finally ran in both cases. That is the guarantee you build cleanup on.

A host check that survives bad luck

Here is a complete check that turns every common network failure into a short, readable answer instead of a crash:

robust_check.py

# robust_check.py: a host check that survives bad input and bad luck
import socket

def check_host(hostname, port=80, timeout=2.0):
    try:
        ip = socket.gethostbyname(hostname)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(timeout)
        sock.connect((ip, port))
        return "reachable"
    except socket.gaierror:
        return "name did not resolve"
    except socket.timeout:
        return "timed out"
    except ConnectionRefusedError:
        return "connection refused"
    except Exception as exc:
        return f"unexpected error: {exc}"
    finally:
        sock.close() if "sock" in locals() else None

for host in ["example.com", "no-such-host.invalid", "10.255.255.1"]:
    print(f"{host}: {check_host(host)}")

Sample output

example.com: reachable
no-such-host.invalid: name did not resolve
10.255.255.1: timed out

Each except names a specific failure and answers it in plain words. Python checks them from the top and runs the first one that matches. finally closes the socket if one was created; the odd-looking "sock" in locals() test is there because the very first line, the DNS lookup, can fail before any socket exists.

Python concept: In Python 3.10 and later, socket.timeout is TimeoutError

You will see both names. In current Python they are the same thing, so except socket.timeout and except TimeoutError catch the identical error. Older tutorials use socket.timeout; newer code often writes TimeoutError. Either works.

The exceptions a network throws at a script

Each of these is a specific kind of failure a real network throws at real code.

ExceptionWhen it is raised
socket.timeoutA connection attempt or a read took longer than the timeout allowed
socket.gaierrorA hostname could not be resolved to an address
ConnectionRefusedErrorThe target actively refused the connection (a closed port)
PermissionErrorThe script tried an action the operating system denied, such as raw sockets without root
FileNotFoundErrorA script tried to open a file, such as a wordlist, that does not exist
KeyErrorA dictionary lookup used a key that is not present

You can make five of them happen on purpose and read what each looks like. Start a throwaway web server on port 9999 for the successful cases, and leave 9998 empty so it refuses:

triggers.py

import socket

def show(label, fn):
    try:
        fn()
    except Exception as exc:
        print(f"{label:<28} -> {type(exc).__name__}: {str(exc)[:55]}")

def refused():
    s = socket.socket(); s.settimeout(3); s.connect(("127.0.0.1", 9998))
def timeout():
    s = socket.socket(); s.settimeout(1); s.connect(("10.255.255.1", 80))
def dns():
    socket.gethostbyname("no-such-host.invalid")
def missing():
    open("wordlist.txt")
def key():
    {"ip": "10.0.0.9"}["os_guess"]

show("closed port", refused)
show("silent host", timeout)
show("unknown name", dns)
show("missing wordlist", missing)
show("missing dictionary key", key)

Sample output (the message text differs by operating system; the exception names do not)

closed port                  -> ConnectionRefusedError: [WinError 10061] No connection could be made because th
silent host                  -> TimeoutError: timed out
unknown name                 -> gaierror: [Errno 11001] getaddrinfo failed
missing wordlist             -> FileNotFoundError: [Errno 2] No such file or directory: 'wordlist.txt'
missing dictionary key       -> KeyError: 'os_guess'

This output came from a Windows machine. On Linux the first line would read ConnectionRefusedError: [Errno 111] Connection refused and the third gaierror: [Errno -2] Name or service not known. The exception names, which are what your except clauses use, are identical on every system. PermissionError is the one you cannot trigger portably: it appears when an ordinary user asks for something only an administrator may do, such as opening a raw socket.

One bad host must not end the scan

This is the reason exceptions matter to a security analyst. A scan list with three bad entries among five, and the script reaches the end anyway:

resilient.py

import socket

def check(host, port, timeout=3.0):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    try:
        sock.connect((host, port))
        return "open"
    except socket.gaierror:
        return "name did not resolve"
    except ConnectionRefusedError:
        return "closed"
    except socket.timeout:
        return "no reply"
    finally:
        sock.close()

targets = [("127.0.0.1", 9999), ("no-such-host.invalid", 80),
           ("127.0.0.1", 9998), ("10.255.255.1", 80), ("127.0.0.1", 9999)]
for host, port in targets:
    print(f"{host}:{port:<5} {check(host, port)}")
print("scan finished: every target was reached, even after three failures")

Sample output

127.0.0.1:9999  open
no-such-host.invalid:80    name did not resolve
127.0.0.1:9998  closed
10.255.255.1:80    no reply
127.0.0.1:9999  open
scan finished: every target was reached, even after three failures

Without the try, the second target would have crashed the script and targets three, four and five would never have been checked. With it, each failure becomes one honest line in the report.

Try it yourself: Break it, then fix it

Remove the try, except and finally from check() and run the script again. It stops at the second target with a traceback. Put the net back and it finishes. Seeing the crash once, with your own eyes, is the fastest way to believe in exception handling.

The net can hide the bug

Caution: Catching the bare Exception is a last resort

Catching the bare Exception class is a safety net of last resort, never a substitute for naming the specific errors you expect. A script that silently swallows every error can hide a real bug for weeks. Catch what you expect by name; catch everything else only to log it before re-raising or reporting it.

To see how dangerous that is, here are two versions of the same check. Both contain the same typo: conect instead of connect.

buggy.py

import socket

def is_open_buggy(host, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.conect((host, port))          # typo: conect
        sock.close()
        return True
    except Exception:
        return False                       # swallows EVERYTHING, including the typo

def is_open_visible(host, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.conect((host, port))
        sock.close()
        return True
    except (socket.timeout, ConnectionRefusedError):
        return False                       # only the failures we expect

print("buggy:  port 9999 open?", is_open_buggy("127.0.0.1", 9999))
try:
    print("visible: port 9999 open?", is_open_visible("127.0.0.1", 9999))
except AttributeError as exc:
    print("visible: the bug shows itself ->", exc)

Sample output (port 9999 really is open)

buggy:  port 9999 open? False
visible: the bug shows itself -> 'socket' object has no attribute 'conect'

The buggy version says the port is closed, when it is open. It will say "closed" for every port it is ever asked about, forever, and no error will ever appear. A scanner like that produces a clean report that is completely wrong, and nobody finds out. The visible version names only the errors it expects, so the typo surfaces at once as an AttributeError. Catch by name.

When you genuinely need a broad catch, log first and then re-raise, so the failure is recorded but not buried:

reraise.py

import socket

def risky():
    s = socket.socket(); s.settimeout(3)
    try:
        s.connect(("127.0.0.1", 9998))
    except Exception as exc:
        print(f"LOG: connect failed: {type(exc).__name__}")
        raise                              # log it, then let it continue upward
    finally:
        s.close()

try:
    risky()
except ConnectionRefusedError:
    print("caller handled it: the port is closed")

Sample output

LOG: connect failed: ConnectionRefusedError
caller handled it: the port is closed

A bare raise inside an except block sends the same error on up to whoever called, after you have logged it. The log has the evidence; the caller still gets to decide what to do.

Case study: Scenario: the clean report that was wrong

An analyst's scanner reports "0 open ports" across a whole subnet, three nights running. Management is relieved. The cause is a library upgrade that renamed one function. The scanner's blanket except Exception: pass swallowed the resulting error on every single host, so every port looked closed. Nothing was ever actually scanned. The fix was a single raise after logging; the cost of the missing log was three nights of false confidence. This scenario is illustrative, but silent failure like this is one of the commonest ways a security script goes wrong.

Check your understanding

Question 1: When does the finally block run, and why does socket cleanup belong there?

finally runs whether or not an error occurred in the try block. Cleanup such as sock.close() belongs there so the socket is always released, even when the connection attempt failed.

Question 2: Name three exceptions a network script meets often and say when each is raised.

Any three of: socket.timeout (a connection or read took longer than allowed), socket.gaierror (a hostname could not be resolved), ConnectionRefusedError (the target refused, so a closed port), PermissionError (the OS denied the action, e.g. raw sockets without root), FileNotFoundError (a file such as a wordlist does not exist), KeyError (a dictionary key is missing).

Question 3: Why is "except Exception: pass" dangerous in a scanner?

It swallows every error, including bugs in your own code. The scanner can fail on every host and still produce a clean-looking report, so the problem can go unnoticed for weeks. Catch the specific errors you expect, and log anything else before re-raising it.

Question 4: A scan list contains one hostname that does not exist. What do you want the script to do?

Catch the socket.gaierror, record "name did not resolve" for that host, and continue to the next target. One bad entry must never stop the other 999.

Key takeaway: Key takeaway

Use try, except and finally so one bad host cannot end a scan, put cleanup in finally, and catch errors by name. Treat the bare Exception as a net of last resort: log it, then re-raise it. A script that hides its own failures is worse than one that crashes.

Assessments

Unit Assessment

Python and Networking Foundations

20 questions30 minPass: 60%

Start exam →

Comprehensive Assessment

Python for Cyber Security: Complete Assessment

100 questions120 minPass: 60%

Start final exam →

Related to this topic

Continue learning

Reference material

Cheatsheet

Python for Cyber Security — Cheatsheet

v1.0

Open resource

MCQ Bank

Python for Cyber Security — MCQ Bank

v1.0

Open resource

Question Bank

Python for Cyber Security — Question Bank

v1.0

Open resource

eBook

Python for Cyber Security — eBook

v1.0

Open resource

Discussion

Loading comments…
More articlesTest your knowledge
EZ MentorAI · Free

Exception Handling: Scripts That Do Not Crash

Hi! I'm EZ Mentor

Your AI tutor for
Exception Handling: Scripts That Do Not Crash

Sign in to ask, it's free.

EZ Mentor is AI-powered · Answers may not be perfect · 10 free questions / day

EZ LearnEZ Learn

Structured learning across Reverse Engineering, Cloud Security, and Research Methodology, plus games and practice tools in EZ Arena. Free, and open to anyone.

EZ Learn

  • Courses
  • EZ Articles
  • EZ Watch
  • EZ Talks
  • EZ Bytes
  • Cyber Hygiene

EZ Library

  • All Resources
  • eBooks & PDFs
  • Question Banks
  • MCQ Banks
  • Cheatsheets

EZ Exams

  • All Exams
  • REMA
  • Cloud Security
  • Research Methodology
  • Python for Cyber Security
  • Cyber Law & IPR
  • Community
  • EZ Board

EZ Arena

  • EZ Games
  • Live Quiz
  • ML Challenges
  • दिशा - Hygiene
  • सजग - Awareness
  • अभ्यास - Practice

Platform

  • About
  • Contact
  • Terms of Use
  • Disclaimer
  • Verify Certificate
  • Events

© 2026 EZ Learn by EpochZero. Educational content for learning purposes.

Platform Lead: Ashish Revar