I Built a Server in Spain Just to Book a Fingerprint Appointment
To book a Spanish TIE fingerprint appointment for a family member, I went from AWS and SOCKS5 to Chrome Native Messaging, and still ended up blocked by Cl@ve.
I have recently been helping a family member apply for Spain’s non-lucrative residence visa.
After quite a few twists and turns, the visa was finally approved, and they entered Spain successfully. The next step was to book a Toma de huellas appointment at a police station: have their fingerprints taken and apply for the physical TIE residence card.
I thought this part would be simple. Open the website, enter some information, choose a time.
Instead, the first problem was not preparing the documents. It was finding a way to open the Spanish government appointment website.
I Could Not Open This Website Outside Spain
I tried from both China and Sweden. Each time I entered the appointment process, the page kept spinning and eventually said it could not be reached.
There was no explanation. It just sat there silently, leaving me to wonder whether the browser was broken, the network was broken, or the website had simply stopped working again.
My instinct was that the website might only accept visits coming from Spain. To test that, I used GlobalPing to send a request from a Spanish node. The page responded normally.
So I bought several commercial VPN services with Spanish locations. The VPNs worked. The IP addresses assigned to me appeared to be in Spain. But the appointment website still would not open.
My guess was that the website did not only restrict the country. It also blocked many of the Spanish IP addresses commonly used by commercial VPNs.
If the usual VPNs did not work, I would make my own.
Back to Familiar Ground: Building a Server in Spain
I opened the AWS console, switched to the Spain region, and created an EC2 instance.
There was no need to configure a full VPN. SSH dynamic port forwarding was enough: open a local SOCKS5 port, then let the browser access the website through the Spanish EC2 instance.
ssh -i <SSH_KEY> \
-N \
-D 127.0.0.1:1080 \
-o ExitOnForwardFailure=yes \
ubuntu@<EC2_IP>
After creating the tunnel, I used the SOCKS5 proxy to check the public IP. It matched the EC2 address, and the geolocation really was Spain, Aragón, Zaragoza.
So the SSH Tunnel was working.
But having a tunnel did not mean the browser would automatically use it. I also did not want to change the proxy settings for all of macOS. I only needed one browser to use the Spanish server for this one website.
The solution was to launch Chrome with a separate user-data directory, effectively an isolated browser environment, and make only that Chrome process use SOCKS5.
At first, I created a temporary user-data directory with mktemp. The website finally opened. But it did not inherit the cookies, extensions, or login state from my normal Chrome, and running the launch command again would create another new directory.
So I changed it to a persistent user-data directory:
~/Library/Application Support/Google/Chrome-Spain-Visa
This became the final launch command:
open -na "Google Chrome" --args \
--user-data-dir="$HOME/Library/Application Support/Google/Chrome-Spain-Visa" \
--proxy-server="socks5://127.0.0.1:1080" \
--host-resolver-rules="MAP * ~NOTFOUND, EXCLUDE 127.0.0.1" \
--no-first-run \
--no-default-browser-check \
--new-window \
"https://checkip.amazonaws.com/"
This Chrome showed the public IP of the Spanish EC2 instance, and the appointment website finally opened.
At this point, I thought the hardest part was over.
Not even close.
The Website Worked, but Codex Controlled the Wrong Chrome
Opening the website was not enough.
I already knew this would not be a one-time form submission. Most of the time, there are no appointments. You have to come back later, go through the whole process again, and keep checking over and over.
From the beginning, I did not plan to do all of that manually every time. I wanted Codex to control the browser, run through the process, and record it, so I could prepare for repeated automatic checks later.
That meant I needed more than a browser that could open the website. I needed a browser that Codex could control reliably.
My default browser is Brave. But the setup I needed, the ChatGPT Chrome Extension plus Codex browser control, had been tested with Chrome. So this time, I used Chrome.
First, I installed the ChatGPT Chrome Extension in my normal Chrome profile. Codex connected to it and could control it.
But when I checked the public IP from that controlled browser, it still returned my normal IP, not the Spanish one.
So Codex could “control Chrome”, but it was controlling the wrong Chrome instance.
Meanwhile, the dedicated Spain Chrome could open the appointment website through the Spanish EC2, but it did not have the extension installed. Codex could not control it.
The problem looked like this:
Normal Chrome
→ Codex can control it
→ But it does not use the Spanish proxy
Spain Chrome
→ It can open the appointment website
→ But Codex cannot control it
I needed to combine those two things in the same Chrome environment.
I installed and enabled the ChatGPT Chrome Extension inside the dedicated Chrome-Spain-Visa environment. But the extension kept showing:
Install the app to use ChatGPT in Chrome
Except Codex App was already installed.
I tried quitting every Chrome process, re-enabling the extension, and restarting the dedicated environment. Nothing worked. Finally, I found this in the error details:
NATIVE_DISCONNECT Specified native messaging host not found.
That narrowed the problem down to Chrome Native Messaging.
The Real Problem Was Hidden in Native Messaging
The ChatGPT Chrome Extension does not connect directly to Codex App. It uses Chrome’s Native Messaging mechanism to connect to a native host installed locally by Codex App.
Codex and I checked the manifest, the runtime, and the native host one layer at a time. The files were there, and the protocol diagnostics worked. Codex App, the local runtime, and the native host were not broken.
The real problem was that this Chrome could not find the manifest.
At least in this case, with a custom user-data-dir, Chrome looked for the manifest under that user directory’s NativeMessagingHosts folder.
Under a normal installation, Codex App put the manifest in Chrome’s default user directory. But I had launched the Spain browser with a custom directory:
--user-data-dir=".../Google/Chrome-Spain-Visa"
For this Chrome process, Chrome-Spain-Visa was the user data root. It did not go back to the normal Chrome directory to look for the native messaging manifest.
That was why the extension reported:
Specified native messaging host not found.
In the end, I put the manifest already installed by Codex App into the NativeMessagingHosts directory for this Chrome environment. The final location looked like this:
~/Library/Application Support/Google/Chrome-Spain-Visa/
NativeMessagingHosts/
com.openai.codexextension.json
After restarting Chrome, the extension finally connected.
This time, when I checked the public IP inside the Chrome actually controlled by Codex, it returned the Spanish EC2 address. Both requirements were finally true:
- This Chrome accessed the website through the Spanish server.
- Codex was controlling this exact Chrome.
There were really two separate paths.
Codex controlling the browser:
Codex App
↔ native host
↔ ChatGPT Chrome Extension
↔ Chrome-Spain-Visa browser
The browser accessing the website:
Chrome-Spain-Visa browser
→ SOCKS5 127.0.0.1:1080
→ SSH Tunnel
→ AWS Spain EC2
→ Spanish appointment website
Just to give fingerprints in Spain, I had now built a server in Spain and debugged Chrome Native Messaging along the way.
Finally, the Actual Appointment Process
Because I would need to check repeatedly, this first run could not be something I clicked through once and forgot.
I asked Codex to operate the browser under my instructions and record what to click on each page, what information to enter, and how to recognize the result. The goal was a complete runbook.
Codex started from the Spanish government’s Cita previa de extranjería appointment page, selected Barcelona, left the office set to any location, chose the TIE fingerprint procedure, entered the flow without Cl@ve, and filled in the NIE, name, and nationality.
After all that work, I finally reached the step that actually checked for appointments.
The page said:
En este momento no hay citas disponibles
para la reserva sin cl@ve.
Without Cl@ve, there were no appointments.
Immediately below that, in bold, the page said:
SÍ TIENEN A SU DISPOSICIÓN MEDIANTE EL USO DE CL@VE,
CITAS DISPONIBLES PARA SU RESERVA.
But with Cl@ve, appointments were available.
Cl@ve is Spain’s electronic identity system. The problem is that, for someone who has just received a visa, just arrived in Spain, and is applying for a TIE for the first time, not having Cl@ve is perfectly normal.
Same procedure. Same province. Same moment. Yet the site said availability depended on whether you already had a Spanish electronic identity.
The whole thing felt like a circular dependency.
After completing the process once, I asked Codex to replay the entire thing, starting from checking the browser’s public IP. On the second run, Codex completed everything from the government appointment page to the final “no appointments without Cl@ve” result automatically.
What we had saved was not just a chat transcript. It was a process we could actually repeat.
This Was Not Just AI Clicking Buttons for Me
What interested me about this was not that Codex clicked some buttons for me.
The useful part was taking a messy, one-off process and making it repeatable. The browser went through the Spanish EC2, the Chrome environment was isolated, Codex controlled the right one, and every step had a known checkpoint and failure point.
Later, I could simply tell Codex:
Check again whether there are any appointments.
It could run through the process again using the runbook.
For my own needs, Codex plus a runbook is already enough. But once I reached this point, I started wondering: am I really the only person dealing with this?
Could This Become a Small Product?
If many people arriving in Spain are stuck in the same situation, maybe this could become a small browser-use tool.
The applicant’s personal information would stay on their own machine. The tool would check the network, replay the process, and notify the user when a slot became available. If it encountered a security warning, CAPTCHA, or final confirmation, it would stop and hand control back to the user instead of continuing by itself.
It is nowhere near a real product yet. I still do not know whether the website allows scheduled checks, how often it would be safe to check, how to keep the SSH Tunnel alive, or how to recover when the browser disconnects.
And there is a more immediate problem: I have not even managed to book one appointment yet.
But the problem has already changed from “a visa headache for my family” into a product question I want to keep thinking about.
Before I think too far ahead, I still need an answer to the most practical question:
When will Spain finally let someone who has just arrived—and does not yet have Cl@ve—book an appointment?