This article will show how to check if a given Onion Service (ie some .onion address) is alive or dead.
Why?
Lots of Onion Services are “here today, gone tomorrow”. If you encountered a large list of Onion Services and want to quickly check to see which are still alive, the best way to do that is by querying the Tor network’s Hidden Service Directory (HSDir).
Of course, you could just paste the .onion into your trusty Tor Browser and press <enter> — but that’s only going to work for Onion Services hosting HTTP servers on port 80 or 443. It won’t, for example, tell you if the Onion Service is alive if it runs an XMPP server. Or an IMAP server. Or an SSH server.
No. Rather than trying to query the Onion Service on all possible ports, it’s best to query the Tor Hidden Service Directory (HSDir).
HS What?
Onion Services (by default) publish a “Hidden Service Descriptor” to the Tor network’s distributed Hidden Service Directory (HDir) every 3 hours.
The Hidden Service Descriptor returned by the HDir contains the metadata necessary for a client to connect to the Onion Service.
If a given Onion Service’s Hidden Service Descriptor cannot be found, then the Onion Service is likely dead.
Assumptions
At the time of writing, this guide was tested against the following software and versions:
- Debian 12
- Tor 0.4.7.16
- Python 3.11.2
- python3-stem 1.8.1-2.1
ⓘ Note that v2 Onion Services (22-characters long) were deprecated by the Tor Project in Jun 2021. They no longer work.
v3 Onion Services (62-characters long) have replaced them.
To query Hidden Service Descriptors for v3 Onion Services, you’ll need stem >= v1.8, which added HiddenServiceDescriptorV3() support
If you’re using a different OS or software versions, then adaptations to the below commands may be necessary.
Check Onion Service
This section will show how you can check to see if a given Onion Service is alive.
Prereqs
Before you can query the Tor network, you first must install Tor and start the Tor daemon.
Execute the following commands to install (and start) Tor.
# install Tor sudo apt-get install tor # star the Tor daemon sudo systemctl start tor
Before you can use the stem (Tor) python module to query the Tor network, you first must install python and the stem python module.
Execute the following commands to install python and the required stem python module:
# install python sudo apt-get install python3 # install the stem python module sudo apt-get install python3-stem
Before you can use stem to query the Tor network, you must modify the tor config to enable a Control Port — where stem will be able to interact directly (and “control”) the tor daemon (as opposed to simply use Tor to make Internet connections).
Execute the followig commands to uncomment the line “#ControlPort 9051” from the ‘/etc/tor/torrc‘ config file (just delete the leading hash character [#] and save the file):
# make a backup of the tor config file sudo cp /etc/tor/torrc /etc/tor/torrc.bak # uncomment the line "ControlPort 9051" sudo vim /etc/tor/torrc
After editing the ‘/etc/tor/torrc‘ config file (eg in ‘vim‘), you should see the following difference
user@disp6307:~$ sudo diff /etc/tor/torrc.bak /etc/tor/torrc 56c56 < #ControlPort 9051 --- > ControlPort 9051 user@disp6307:~$
Finally, restart the tor daemon to apply the change:
# restart the tor daemon sudo systemctl restart tor
The python
Execute ‘sudo python3‘ to open a python interpreter shell as root
user@disp6307:~$ sudo python3 Python 3.11.2 (main, Nov 30 2024, 21:22:50) [GCC 12.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>
At the prompt, import our required ‘Controller‘ object from the ‘stem‘ python module
>>> from stem.control import Controller >>>
At the prompt, create a new instance of the ‘Controller‘ object. Make sure the port number here (eg ‘9051‘) matches the number following ‘ControlPort‘ in the ‘/etc/tor/torrc‘ file that you uncommented above.
>>> controller = Controller.from_port(port=9051) >>>
At the prompt, tell the new controller to connect to your local Tor daemon using the ‘authenticate()‘ method:
>>> controller.authenticate() >>>
At the prompt, confirm that the controller’s connection to the Tor daemon is working using the ‘is_alive()‘ method:
>>> controller.is_alive() True >>>
Now you can ask the controller to get the Hidden Service Descriptor for a given Onion Service — just strip-off the ‘.onion‘ at the end of your Onion Service. In this example, we’ll use the DuckDuckGo Onion Serivce duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion:
>>> controller.get_hidden_service_descriptor( 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad' ) <stem.descriptor.hidden_service.HiddenServiceDescriptorV2 object at 0x7b3ddf115710> >>>
If you didn’t get an error, then the Onion Service is alive!
For comparison, here’s a nonsense Onion Service that does *not* exist (and returns a ProtocolError Exception):
>>> controller.get_hidden_service_descriptor( 'thisverylikelydoesnotexistandihopeitdoesnotexistfortestd' )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/stem/control.py", line 489, in wrapped
return func(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3/dist-packages/stem/control.py", line 2188, in get_hidden_service_descriptor
raise stem.ProtocolError('HSFETCH returned unexpected response code: %s' % response.code)
stem.ProtocolError: HSFETCH returned unexpected response code: 513
>>>
Scripted Example
Recently I was searching for an XMPP server that was hosted on an Onion Service. Unfortunately, the majority of the XMPP server lists that I found were very out-of-date, and I needed a way to check a list of dozens of Onion Services to see which were still operational and which were dead.
So I wrote the following python script, and saved it as ‘get_hidden_service_descriptor.py‘
from stem.control import Controller
from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
onions = ['2n3tvihf4n27pqyqdtcqywl33kbjuv2kj3eeq6qvbtud57jwiaextmid.onion', '32qywqnlnqzbry42nmotr47ebts3k6lhiwfob6xniosmepz2tsnsx7ad.onion', '4colmnerbjz3xtsjmqogehtpbt5upjzef57huilibbq3wfgpsylub7yd.onion', '6voaf7iamjpufgwoulypzwwecsm2nu7j5jpgadav2rfqixmpl4d65kid.onion', '6w5iasklrbr2kw53zqrsjktgjapvjebxodoki3gjnmvb4dvcbmz7n3qd.onion', '7drfpncjeom3svqkyjitif26ezb3xvmtgyhgplcvqa7wwbb4qdbsjead.onion', 'ae3w7fkzr3elfwsk6mhittjj7e7whme2tumdrhw3dfumy2hsiwomc3yd.onion', 'chillingguw3yu2rmrkqsog4554egiry6fmy264l5wblyadds3c2lnyd.onion', 'fzdx522fvinbaqgwxdet45wryluchpplrkkzkry33um5tufkjd3wdaqd.onion', 'gku6irp4e65ikfkbrdx576zz6biapv37vv2cmklo2qyrtobugwz5iaad.onion', 'gois4b6fahhrlsieupl56xd6ya226m33abzuv26vgfpuvv44wf6vbdad.onion', 'j4dhkkxfcsvzvh3p5djkmuehhgd6t6l7wmzih6b4ss744hegwkiae7ad.onion', 'jabjabdea2eewo3gzfurscj2sjqgddptwumlxi3wur57rzf5itje2rid.onion', 'jaswtrycaot3jzkr7znje4ebazzvbxtzkyyox67frgvgemwfbzzi6uqd.onion', 'jeirlvruhz22jqduzixi6li4xyoweytqglwjons4mbuif76fgslg5uad.onion', 'jukrlvyhgguiedqswc5lehrag2fjunfktouuhi4wozxhb6heyzvshuyd.onion', 'mrbenqxl345o4u7yaln25ayzz5ut6ab3kteulzqusinjdx6oh7obdlad.onion', 'nixnet54icmeh25qsmcsereuoareofzevjqjnw3kki6oxxey3jonwwyd.onion', 'qawb5xl3mxiixobjsw2d45dffngyyacp4yd3wjpmhdrazwvt4ytxvayd.onion', 'qwikoouqore6hxczat3gwbe2ixjpllh3yuhaecixyenprbn6r54mglqd.onion', 'qwikxxeiw4kgmml6vjw2bsxtviuwjce735dunai2djhu6q7qbacq73id.onion', 'razpihro3mgydaiykvxwa44l57opvktqeqfrsg3vvwtmvr2srbkcihyd.onion', 'rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion', 'szd7r26dbcrrrn4jthercrdypxfdmzzrysusyjohn4mpv2zbwcgmeqqd.onion', 'xdkriz6cn2avvcr2vks5lvvtmfojz2ohjzj4fhyuka55mvljeso2ztqd.onion', 'xiynxwxxpw7olq76uhrbvx2ts3i7jagqnqix7arfbknmleuoiwsmt5yd.onion', 'xmppccwrohw3lmfap6e3quep2yzx3thewkfhw4vptb5gwgnkttlq2vyd.onion', 'ynnuxkbbiy5gicdydekpihmpbqd4frruax2mqhpc35xqjxp5ayvrjuqd.onion', 'yxkc2uu3rlwzzhxf2thtnzd7obsdd76vtv7n34zwald76g5ogbvjbbqd.onion']
controller = Controller.from_port(port=9051)
controller.authenticate()
if controller.is_alive():
print( "INFO: Confirmed that Tor is working." )
print( "INFO: Trying get_hidden_service_descriptor()" )
for onion in onions:
print( onion )
try:
desc = controller.get_hidden_service_descriptor(onion)
print( "\tOK" )
except Exception as e:
print( "\tNo Descriptor Found" )
Here’s an example execution, which shows that 7 out of 29 of the Onion Services are dead.
user@disp6307:~$ time sudo python3 get_hidden_service_descriptor.py INFO: Confirmed that Tor is working. INFO: Trying get_hidden_service_descriptor() 2n3tvihf4n27pqyqdtcqywl33kbjuv2kj3eeq6qvbtud57jwiaextmid.onion OK 32qywqnlnqzbry42nmotr47ebts3k6lhiwfob6xniosmepz2tsnsx7ad.onion OK 4colmnerbjz3xtsjmqogehtpbt5upjzef57huilibbq3wfgpsylub7yd.onion OK 6voaf7iamjpufgwoulypzwwecsm2nu7j5jpgadav2rfqixmpl4d65kid.onion OK 6w5iasklrbr2kw53zqrsjktgjapvjebxodoki3gjnmvb4dvcbmz7n3qd.onion No Descriptor Found 7drfpncjeom3svqkyjitif26ezb3xvmtgyhgplcvqa7wwbb4qdbsjead.onion No Descriptor Found ae3w7fkzr3elfwsk6mhittjj7e7whme2tumdrhw3dfumy2hsiwomc3yd.onion OK chillingguw3yu2rmrkqsog4554egiry6fmy264l5wblyadds3c2lnyd.onion OK fzdx522fvinbaqgwxdet45wryluchpplrkkzkry33um5tufkjd3wdaqd.onion OK gku6irp4e65ikfkbrdx576zz6biapv37vv2cmklo2qyrtobugwz5iaad.onion OK gois4b6fahhrlsieupl56xd6ya226m33abzuv26vgfpuvv44wf6vbdad.onion OK j4dhkkxfcsvzvh3p5djkmuehhgd6t6l7wmzih6b4ss744hegwkiae7ad.onion OK jabjabdea2eewo3gzfurscj2sjqgddptwumlxi3wur57rzf5itje2rid.onion OK jaswtrycaot3jzkr7znje4ebazzvbxtzkyyox67frgvgemwfbzzi6uqd.onion OK jeirlvruhz22jqduzixi6li4xyoweytqglwjons4mbuif76fgslg5uad.onion OK jukrlvyhgguiedqswc5lehrag2fjunfktouuhi4wozxhb6heyzvshuyd.onion No Descriptor Found mrbenqxl345o4u7yaln25ayzz5ut6ab3kteulzqusinjdx6oh7obdlad.onion No Descriptor Found nixnet54icmeh25qsmcsereuoareofzevjqjnw3kki6oxxey3jonwwyd.onion OK qawb5xl3mxiixobjsw2d45dffngyyacp4yd3wjpmhdrazwvt4ytxvayd.onion No Descriptor Found qwikoouqore6hxczat3gwbe2ixjpllh3yuhaecixyenprbn6r54mglqd.onion OK qwikxxeiw4kgmml6vjw2bsxtviuwjce735dunai2djhu6q7qbacq73id.onion OK razpihro3mgydaiykvxwa44l57opvktqeqfrsg3vvwtmvr2srbkcihyd.onion No Descriptor Found rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion OK szd7r26dbcrrrn4jthercrdypxfdmzzrysusyjohn4mpv2zbwcgmeqqd.onion OK xdkriz6cn2avvcr2vks5lvvtmfojz2ohjzj4fhyuka55mvljeso2ztqd.onion OK xiynxwxxpw7olq76uhrbvx2ts3i7jagqnqix7arfbknmleuoiwsmt5yd.onion OK xmppccwrohw3lmfap6e3quep2yzx3thewkfhw4vptb5gwgnkttlq2vyd.onion OK ynnuxkbbiy5gicdydekpihmpbqd4frruax2mqhpc35xqjxp5ayvrjuqd.onion No Descriptor Found yxkc2uu3rlwzzhxf2thtnzd7obsdd76vtv7n34zwald76g5ogbvjbbqd.onion OK real 0m53.311s user 0m0.005s sys 0m0.006s user@disp6307:~$
Limitations
This section will explain limitations to the information outlined in this article, as well as suggest potential improvements.
PublishHidServDescriptors
This method won’t work if the Onion Service has set ‘PublishHidServDescriptors‘ to ‘0‘.
But if an Onion Service doesn’t publish its Hidden Service Descriptors, then it’s not alive anyway — since clients won’t be able to obtain the necessary metadata from the Tor network to connect to it.
Further Reading
- torproject.org Official Tor Project Website
- stem.torproject.org Official Stem Project Website
- forum.torproject.org Official Tor Project Support Forums
- stem.torproject.org/tutorials/over_the_river.html Stem Tutorial for Hidden Services
- https://github.com/torproject/stem/issues/96 Bug in stem where get_hidden_service_descriptor() returns a v2 descriptor
Related Posts
Hi, I’m Michael Altfield. I write articles about opsec, privacy, and devops ➡












Leave a Reply