Your Arduino is plugged in and your Python script is ready. It crashes on the first line with could not open port 'COM3': PermissionError(13, 'Access is denied.'). When PySerial says access is denied, it's almost never a real permission problem. Another program is already using the port.
The Problem: PySerial Says Access Is Denied
import serial
arduino = serial.Serial("COM3", 9600)
Two lines, and the second one fails. The port name is right and the cable is fine.
How to Read the Error Message
The full line usually looks like SerialException: could not open port 'COM3': PermissionError(13, 'Access is denied.'). Each part says something:
PermissionError(13) – error number 13 is the operating system's generic "that request is not allowed". It covers files, folders and ports alike.
could not open port 'COM3' – PySerial asked Windows to open the port and Windows said no.
Access is denied – on a serial port, this almost always means: someone else has it open.
The misleading part is the word "permission". On Linux, a serial permission error really is about user groups. On Windows it nearly always means the port is busy – and no amount of running your script as administrator will fix that, because administrators get refused too. Only one program at a time can hold a serial port.
Why It Happens
A serial port can be open in only one program at a time. Whoever opens it first keeps it, and everyone else is refused. The usual suspects, in order:
The Arduino IDE. If its Serial Monitor or Serial Plotter is open, it holds the port. Closing the monitor window is not always enough – close the whole IDE if in doubt.
An earlier run of your own script. A terminal from ten minutes ago with a script stuck in a
while True:loop still owns the port. So does a Python shell (IDLE, or apythonsession) where you opened the port and never closed it.Your own code opening the port twice. Two
serial.Serial("COM3", 9600)calls in one script – easy to do in a loop – and the second is denied by your own first one.Other serial software. Anything that talks to boards: Cura and other 3D-printer slicers, flashers, serial terminal apps like PuTTY.
Find What Is Holding the Port
On Windows, press Ctrl+Shift+Esc to open Task Manager, go to the Details tab, and look for every python.exe, pythonw.exe and arduino.exe (or java.exe – the Arduino IDE is Java). End the ones you are not using, then run your script again.
If that finds nothing, unplug the USB cable, count to five, and plug it back in. Unplugging force-releases the port from any program that is keeping it open without a window.
The Fix: Close Whatever Holds the Port
Close the Serial Monitor and the Serial Plotter in the Arduino IDE.
Stop any earlier run of your script. In VS Code, click the bin icon on each old terminal.
Still refused? Unplug the USB cable, wait a few seconds and plug it back in.
It works the other way round too. While your Python script holds the port, the Arduino IDE can't upload a new sketch. Stop the script first – you will see "Upload failed" or "Access is denied" in the IDE's status bar otherwise.
Close the Port in Your Own Code
Open the port with a with block, and Python closes it for you, even when the script crashes halfway:
import serial
import time
with serial.Serial("COM3", 9600, timeout=1) as arduino:
time.sleep(2) # the board restarts when the port opens
arduino.write(b"R")
reply = arduino.readline().decode().strip()
print(reply)
The time.sleep(2) matters. Most Arduino boards, the Uno included, restart when a program opens the serial port. Send data in the first second or two and the board misses it, because it's still starting up.
The timeout=1 matters just as much. Without it, readline() waits forever if the board never sends a newline, and your script hangs – looking, from the outside, a lot like the port problem returning. With a timeout, readline() returns whatever arrived (an empty bytes object if nothing did) after one second, and your script keeps running.
If you prefer opening the port the plain way, close it on every path – including the error path:
arduino = serial.Serial("COM3", 9600, timeout=1)
try:
arduino.write(b"R")
finally:
arduino.close()
Wrong Port Name: A Different Error
If the port doesn't exist, the message changes to FileNotFoundError. List the ports your PC can see with:
python -m serial.tools.list_ports
The number often changes when you use a different USB socket, so COM3 yesterday can be COM5 today. Watch the output while you unplug and replug the board – the port that appears and disappears is yours. A few laptops also ship Bluetooth serial ports (often the lowest COM numbers), and picking one of those by mistake gives you either this same access-denied error or connection after which nothing arrives.
Can Two Programs Share the Port?
Not natively – the one-program rule is built into the operating system's serial driver, on Windows and Linux alike. Tools exist that create virtual port pairs and split the data, but for Arduino work the honest answer is simpler: read the reply in the same program that opens the port, and close it when you are done. If you need the Serial Monitor and your script at the same time, make your script print what it receives, and close the IDE.
Still Denied? The Last Three Causes
If Task Manager is empty, the cable is out and back in, and the error still shows, work down this list:
The USB controller is stuck. In Device Manager, find "Ports (COM & LPT)", right-click your board's entry, choose Uninstall device, then unplug and replug. Windows rebuilds the port from scratch.
A driver took the port hostage. Some antivirus and "device control" tools block raw serial access. If your company laptop denies every port including ones with nothing plugged in, that is what is happening – it is an IT policy, not PySerial.
A clone board with broken USB drivers. Unos use ATmega16U2 for USB (built into Windows), but many clones use a CH340 chip that needs its own driver. The giveaway: the board appears in Device Manager with a yellow warning icon. Install the CH340 driver and both the warning and the strangeness go away.
Which Error Means What
All three PySerial opening errors sound alike the first week. The fast way to tell them apart:
PermissionError(13) / Access is denied – the port exists but someone else has it open. Close the holder.
FileNotFoundError(2) / No such file or directory – the port name is wrong or the board is unplugged. Run the list_ports command.
SerialException: Port is already open – your own Python object opened the port twice. Reuse the object or close it first.
PySerial Access Is Denied on Linux
On Linux the error reads Permission denied: '/dev/ttyACM0', and there it really is about permissions. Add your user to the group that owns serial ports, then log out and back in:
sudo usermod -a -G dialout $USER
On Windows, though, a PySerial access is denied error means the port is busy. The PySerial short introduction shows more ways to open and read a port.
To see serial used in a full project, read AI agents with hardware integration. And if your board stops answering while it blinks, the cause is probably delay() freezing your Arduino.
What was holding your port: the Serial Monitor or an old script? Tell me in the comments.
Originally published on my blog. I post new C, Python and AI-agent guides there regularly.

Top comments (0)